Env setup

# install.packages(renv)
# renv::init()
# renv::install("reticulate")
# renv::use_python()
# 
# py_pkgs <- c(
#     "scanpy",
#     "anndata",
#     "mofapy2"
# )
# 
# reticulate::py_install(py_pkgs)
# 
# BiocManager::install(c("SingleCellExperiment", "scran", "batchelor", "scater"))
# install.packages(c("patchwork"))
renv::activate()
* Project '/nfs/team205/ed6/bin/Pan_fetal_immune/src/5_organ_signatures/MOFA_factor_analysis' loaded. [renv 0.12.5]
suppressPackageStartupMessages({
  library(tidyverse)
  library(MOFA2)
  library(Matrix)
  library(SingleCellExperiment)
  library(scran)
  library(glue)
  library(scater)
  library(patchwork)
  library(batchelor)
  library(rhdf5)
  # library(ggraph)
  }
  )
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to create bus connection: Host is down
running command 'timedatectl' had status 1package ‘ggplot2’ was built under R version 4.0.5package ‘tibble’ was built under R version 4.0.5package ‘tidyr’ was built under R version 4.0.5package ‘readr’ was built under R version 4.0.5package ‘dplyr’ was built under R version 4.0.5package ‘matrixStats’ was built under R version 4.0.5

Define plotting utils

remove_x_axis <- function(){
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())  
}

remove_y_axis <- function(){
  theme(axis.text.y = element_blank(), axis.ticks.y = element_blank(), axis.title.y = element_blank())  
}

org_colors <- read_csv("~/Pan_fetal_immune/metadata/organ_colors.csv")
New names:
* `` -> ...1
Rows: 9 Columns: 3
── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (2): organ, color
dbl (1): ...1

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
org_colors <- setNames(org_colors$color, org_colors$organ)
figdir <- "~/mount/gdrive/Pan_fetal/Updates_and_presentations/figures/MOFA_analysis_MYELOID/"
if (!dir.exists(figdir)){ dir.create(figdir) }

Load pseudobulked data

split = "MYELOID"
indir <- glue("/nfs/team205/ed6/data/Fetal_immune/LMM_data/LMM_input_{split}_PBULK/")

matrix <- readMM(file = paste0(indir, "matrix.mtx.gz"))
coldata <- read.csv(file = paste0(indir, "metadata.csv.gz"))  %>%
  column_to_rownames("X")
rowdata <- read.csv(file = paste0(indir, "gene.csv.gz")) 

## Make SingleCellExperiment obj
sce <- SingleCellExperiment(list(logcounts = t(matrix)), colData = coldata)
rownames(sce) <- make.unique(rowdata$GeneName) 
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

Preprocessing

Filtering samples

## Filter out samples with less than 20 cells
sce <- sce[,sce$n_cells > 20]

# Exclude celltypes present in just one organ
keep_ct <- data.frame(colData(sce)) %>%
  dplyr::select(organ, anno_lvl_2_final_clean) %>%
  distinct() %>%
  group_by(anno_lvl_2_final_clean) %>%
  summarise(n=n()) %>%
  ungroup() %>%
  filter(n > 1) %>%
  pull(anno_lvl_2_final_clean)

sce <- sce[,sce$anno_lvl_2_final_clean %in% keep_ct]

# Filter out celltypes with less than 10 samples
keep_ct <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean) %>%
  summarise(n_samples=n()) %>%
  filter(n_samples >= 10) %>%
  pull(anno_lvl_2_final_clean)

sce <- sce[,sce$anno_lvl_2_final_clean %in% keep_ct]

## Exclude low quality clusters
anno_groups <- jsonlite::fromJSON(txt =  "~/Pan_fetal_immune/metadata/anno_groups.json")
sce <- sce[,!sce$anno_lvl_2_final_clean %in% anno_groups$OTHER]

## Exclude donor F19 (low Q)
sce <- sce[,!sce$donor %in% c('F19')]
## Plot number of cells per organ/celltype pair
n_cells_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_cells=sum(n_cells)) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=log10(n_cells))) +
  geom_text(aes(label=n_cells), color="white") +
  scale_fill_viridis_c() +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_blank(), axis.ticks.x = element_blank(), axis.title.x = element_blank())
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_samples_heatmap <- data.frame(colData(sce)) %>%
  group_by(anno_lvl_2_final_clean, organ) %>%
  summarise(n_samples=n()) %>%
  ggplot(aes(anno_lvl_2_final_clean, organ)) +
  geom_tile(aes(fill=n_samples)) +
  geom_text(aes(label=n_samples), color="white") +
  scale_fill_viridis_c(option="cividis") +
  theme_classic(base_size = 16) +
  xlab("celltype") +
  theme(axis.text.x = element_text(angle=90, hjust=1, vjust=0.5))
`summarise()` has grouped output by 'anno_lvl_2_final_clean'. You can override using the `.groups` argument.
n_cells_heatmap / n_samples_heatmap

## Save order of CTs from widespread to restricted
levels(pl$data$anno_lvl_2_final_clean)
 [1] "DC2"                                 "CD16+_MACROPHAGE"                   
 [3] "CD14_MONO"                           "PROLIFERATING_MACROPHAGE"           
 [5] "CD14+_MACROPHAGE"                    "DC3"                                
 [7] "DC1"                                 "EO_BASO_MAST"                       
 [9] "LMPP_ELP"                            "OLFML3+_MICROGLIA"                  
[11] "YS_MACROPHAGE"                       "KUPFFER_RP_MACROPHAGE"              
[13] "PROMONOCYTE_(PROLIFERATING)"         "BM_CD14_MONO"                       
[15] "HSC_MPP"                             "PRE_PRO_B"                          
[17] "MEMP"                                "CMP"                                
[19] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CYCLING_MPP"                        
[21] "GMP"                                 "NEUTROPHIL"                         
[23] "SPLENIC_MACROPHAGE"                  "PROMONOCYTE"                        

Technical effect correction

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i])
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

sce <- sce[which(rowSums(logcounts(sce)) > 0),]
sce
class: SingleCellExperiment 
dim: 29568 1030 
metadata(0):
assays(1): logcounts
rownames(29568): TSPAN6 TNMD ... AP000646.1 AP006216.3
rowData names(0):
colnames(1030): F45_SK_CD45P_FCAImmP7579224-F45-SK-DC3-12-5GEX F45_SK_CD45P_FCAImmP7579224-F45-SK-DC2-12-5GEX ...
  F50_SP_CD45P_FCAImmP7803020-F50-SP-CD14_MONO-15-5GEX F30_TH_CD45N_FCAImmP7277565-F30-TH-DC1-14-3GEX
colData names(7): Sample donor ... method n_cells
reducedDimNames(0):
altExpNames(0):

EDA with PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, 
              exprs_values = "logcounts", subset_row=all_hvgs)
plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=10)

Minimize obvious technical effects (3GEX/5GEX, donor) using linear regression (following procedure from OSCA)

## Regress technical effects
design <- model.matrix(~donor+method,data=colData(sce))
residuals <- regressBatches(sce, assay.type = "logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

## Regress organ (soup effect)
design <- model.matrix(~organ,data=colData(sce)) ## Include organ term to capture soup
residuals <- regressBatches(sce, assay.type = "corrected_logcounts", design = design)
assay(sce, "corrected_logcounts") <- as.matrix(assay(residuals[,colnames(sce)], "corrected"))

Check regression has an effect repeating PCA

sce <- runPCA(sce, scale=TRUE, ncomponents=30, exprs_values = "corrected_logcounts")

plotPCA(sce, colour_by="method", ncomponents=6)

plotPCA(sce, colour_by="donor", ncomponents=6)

plotPCA(sce, colour_by="organ", ncomponents=8)

Feature selection

## Feature selection w scran WITHIN CELLTYPE
anno_groups <- split(colnames(sce), sce$anno_lvl_2_final_clean)
all_hvgs <- c()
for (i in anno_groups){
  dec <- modelGeneVar(sce[,i], assay.type = "corrected_logcounts")
  hvgs <- getTopHVGs(dec, n = 1000)
  all_hvgs <- union(all_hvgs, hvgs)
  }

FA Model - Normal MOFA / only celltypes as groups

Make MOFA object (Use celltypes as grouping covariate)

str_replace(sce$anno_lvl_2_final_clean, "/","_")
   [1] "DC3"                                 "DC2"                                 "PROLIFERATING_MACROPHAGE"           
   [4] "EO_BASO/MAST"                        "CD14_MONO"                           "CD14+_MACROPHAGE"                   
   [7] "DC3"                                 "PROLIFERATING_MACROPHAGE"            "CYCLING_MPP"                        
  [10] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                    "KUPFFER_RP_MACROPHAGE"              
  [13] "CD14+_MACROPHAGE"                    "KUPFFER_RP_MACROPHAGE"               "DC3"                                
  [16] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC2"                                 "CD16+_MACROPHAGE"                   
  [19] "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                    "CD14_MONO"                          
  [22] "DC1"                                 "SPLENIC_MACROPHAGE"                  "DC2"                                
  [25] "DC1"                                 "CD14_MONO"                           "CD14+_MACROPHAGE"                   
  [28] "EO_BASO/MAST"                        "EO_BASO/MAST"                        "CYCLING_MPP"                        
  [31] "CMP"                                 "MEMP"                                "PRE_PRO_B"                          
  [34] "KUPFFER_RP_MACROPHAGE"               "HSC_MPP"                             "OLFML3+_MICROGLIA"                  
  [37] "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                    "CD14+_MACROPHAGE"                   
  [40] "DC1"                                 "DC3"                                 "DC2"                                
  [43] "EO_BASO/MAST"                        "CD14_MONO"                           "CD14+_MACROPHAGE"                   
  [46] "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                    "DC3"                                
  [49] "CMP"                                 "EO_BASO/MAST"                        "CYCLING_MPP"                        
  [52] "GMP"                                 "PRE_PRO_B"                           "PROMONOCYTE_(PROLIFERATING)"        
  [55] "HSC_MPP"                             "PROMONOCYTE"                         "BM_CD14_MONO"                       
  [58] "KUPFFER_RP_MACROPHAGE"               "EO_BASO/MAST"                        "GMP"                                
  [61] "CYCLING_MPP"                         "MEMP"                                "DC3"                                
  [64] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CMP"                                 "HSC_MPP"                            
  [67] "DC1"                                 "CD16+_MACROPHAGE"                    "PRE_PRO_B"                          
  [70] "PROMONOCYTE_(PROLIFERATING)"         "DC2"                                 "CD14_MONO"                          
  [73] "PROLIFERATING_MACROPHAGE"            "LMPP_ELP"                            "CD14+_MACROPHAGE"                   
  [76] "BM_CD14_MONO"                        "DC3"                                 "PROLIFERATING_MACROPHAGE"           
  [79] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CYCLING_MPP"                         "CD16+_MACROPHAGE"                   
  [82] "KUPFFER_RP_MACROPHAGE"               "CD14+_MACROPHAGE"                    "CD14_MONO"                          
  [85] "OLFML3+_MICROGLIA"                   "HSC_MPP"                             "DC2"                                
  [88] "EO_BASO/MAST"                        "LMPP_ELP"                            "KUPFFER_RP_MACROPHAGE"              
  [91] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC2"                                 "DC3"                                
  [94] "CD16+_MACROPHAGE"                    "CD14_MONO"                           "CD14_MONO"                          
  [97] "DC3"                                 "PROMONOCYTE_(PROLIFERATING)"         "DC1"                                
 [100] "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                    "DC2"                                
 [103] "CD16+_MACROPHAGE"                    "BM_CD14_MONO"                        "CMP"                                
 [106] "EO_BASO/MAST"                        "HSC_MPP"                             "BM_CD14_MONO"                       
 [109] "OLFML3+_MICROGLIA"                   "DC3"                                 "DC1"                                
 [112] "PROLIFERATING_MACROPHAGE"            "DC2"                                 "CD14+_MACROPHAGE"                   
 [115] "CD14_MONO"                           "EO_BASO/MAST"                        "CD16+_MACROPHAGE"                   
 [118] "DC3"                                 "DC1"                                 "DC2"                                
 [121] "CD16+_MACROPHAGE"                    "PROLIFERATING_MACROPHAGE"            "EO_BASO/MAST"                       
 [124] "CD14+_MACROPHAGE"                    "CD14_MONO"                           "BM_CD14_MONO"                       
 [127] "CMP"                                 "DC3"                                 "GMP"                                
 [130] "PROMONOCYTE_(PROLIFERATING)"         "PROMONOCYTE"                         "PRE_PRO_B"                          
 [133] "BM_CD14_MONO"                        "EO_BASO/MAST"                        "NEUTROPHIL"                         
 [136] "SPLENIC_MACROPHAGE"                  "DC1"                                 "CD14_MONO"                          
 [139] "DC2"                                 "CD14+_MACROPHAGE"                    "CD16+_MACROPHAGE"                   
 [142] "CMP"                                 "CYCLING_MPP"                         "GMP"                                
 [145] "EO_BASO/MAST"                        "HSC_MPP"                             "MEMP"                               
 [148] "PRE_PRO_B"                           "OLFML3+_MICROGLIA"                   "DC3"                                
 [151] "PROLIFERATING_MACROPHAGE"            "EO_BASO/MAST"                        "PROMONOCYTE_(PROLIFERATING)"        
 [154] "CD14+_MACROPHAGE"                    "BM_CD14_MONO"                        "CD14_MONO"                          
 [157] "DC2"                                 "CD16+_MACROPHAGE"                    "CD16+_MACROPHAGE"                   
 [160] "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                    "CYCLING_MPP"                        
 [163] "CMP"                                 "EO_BASO/MAST"                        "HSC_MPP"                            
 [166] "MEMP"                                "CD14+_MACROPHAGE"                    "KUPFFER_RP_MACROPHAGE"              
 [169] "DC3"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC2"                                
 [172] "CD16+_MACROPHAGE"                    "CD14_MONO"                           "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [175] "KUPFFER_RP_MACROPHAGE"               "DC3"                                 "DC1"                                
 [178] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "DC2"                                
 [181] "CD14_MONO"                           "CD14+_MACROPHAGE"                    "YS_MACROPHAGE"                      
 [184] "BM_CD14_MONO"                        "PROLIFERATING_MACROPHAGE"            "OLFML3+_MICROGLIA"                  
 [187] "DC3"                                 "CD14_MONO"                           "DC2"                                
 [190] "CD14+_MACROPHAGE"                    "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC3"                                
 [193] "KUPFFER_RP_MACROPHAGE"               "CYCLING_MPP"                         "GMP"                                
 [196] "HSC_MPP"                             "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                       
 [199] "CD16+_MACROPHAGE"                    "DC1"                                 "PRE_PRO_B"                          
 [202] "CMP"                                 "CD14_MONO"                           "MEMP"                               
 [205] "DC2"                                 "PROLIFERATING_MACROPHAGE"            "LMPP_ELP"                           
 [208] "CD14+_MACROPHAGE"                    "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC3"                                
 [211] "KUPFFER_RP_MACROPHAGE"               "CD16+_MACROPHAGE"                    "DC2"                                
 [214] "CD14_MONO"                           "DC2"                                 "CD16+_MACROPHAGE"                   
 [217] "DC3"                                 "DC1"                                 "PROMONOCYTE_(PROLIFERATING)"        
 [220] "CD14+_MACROPHAGE"                    "OLFML3+_MICROGLIA"                   "PROLIFERATING_MACROPHAGE"           
 [223] "EO_BASO/MAST"                        "DC2"                                 "CD14_MONO"                          
 [226] "DC3"                                 "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                   
 [229] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "KUPFFER_RP_MACROPHAGE"              
 [232] "CD14_MONO"                           "OLFML3+_MICROGLIA"                   "DC2"                                
 [235] "LMPP_ELP"                            "PROLIFERATING_MACROPHAGE"            "CD16+_MACROPHAGE"                   
 [238] "CD14+_MACROPHAGE"                    "DC2"                                 "DC1"                                
 [241] "DC3"                                 "CD14+_MACROPHAGE"                    "DC2"                                
 [244] "CD14_MONO"                           "PROLIFERATING_MACROPHAGE"            "MEMP"                               
 [247] "NEUTROPHIL"                          "DC3"                                 "DC1"                                
 [250] "PROLIFERATING_MACROPHAGE"            "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "KUPFFER_RP_MACROPHAGE"              
 [253] "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                    "SPLENIC_MACROPHAGE"                 
 [256] "EO_BASO/MAST"                        "DC2"                                 "YS_MACROPHAGE"                      
 [259] "CD14_MONO"                           "PROMONOCYTE_(PROLIFERATING)"         "DC3"                                
 [262] "CMP"                                 "PRE_PRO_B"                           "GMP"                                
 [265] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "KUPFFER_RP_MACROPHAGE"              
 [268] "BM_CD14_MONO"                        "PROMONOCYTE"                         "CD14_MONO"                          
 [271] "NEUTROPHIL"                          "DC1"                                 "CYCLING_MPP"                        
 [274] "CMP"                                 "HSC_MPP"                             "EO_BASO/MAST"                       
 [277] "CD14+_MACROPHAGE"                    "OLFML3+_MICROGLIA"                   "DC3"                                
 [280] "CD14+_MACROPHAGE"                    "CD14_MONO"                           "PROLIFERATING_MACROPHAGE"           
 [283] "LMPP_ELP"                            "DC2"                                 "MEMP"                               
 [286] "DC3"                                 "DC1"                                 "CD14_MONO"                          
 [289] "DC2"                                 "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                   
 [292] "EO_BASO/MAST"                        "CD16+_MACROPHAGE"                    "DC1"                                
 [295] "EO_BASO/MAST"                        "CD14_MONO"                           "DC2"                                
 [298] "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                    "CD16+_MACROPHAGE"                   
 [301] "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                   
 [304] "DC2"                                 "CD14+_MACROPHAGE"                    "CD14_MONO"                          
 [307] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC3"                                 "KUPFFER_RP_MACROPHAGE"              
 [310] "YS_MACROPHAGE"                       "PROLIFERATING_MACROPHAGE"            "DC2"                                
 [313] "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                    "DC3"                                
 [316] "PROLIFERATING_MACROPHAGE"            "YS_MACROPHAGE"                       "KUPFFER_RP_MACROPHAGE"              
 [319] "CD14+_MACROPHAGE"                    "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                   
 [322] "DC1"                                 "DC1"                                 "DC3"                                
 [325] "KUPFFER_RP_MACROPHAGE"               "DC1"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [328] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "DC2"                                
 [331] "SPLENIC_MACROPHAGE"                  "CD14_MONO"                           "PROLIFERATING_MACROPHAGE"           
 [334] "BM_CD14_MONO"                        "CD14+_MACROPHAGE"                    "CMP"                                
 [337] "EO_BASO/MAST"                        "GMP"                                 "HSC_MPP"                            
 [340] "BM_CD14_MONO"                        "DC3"                                 "DC1"                                
 [343] "CD14_MONO"                           "DC2"                                 "CYCLING_MPP"                        
 [346] "EO_BASO/MAST"                        "HSC_MPP"                             "CMP"                                
 [349] "MEMP"                                "CD14_MONO"                           "CYCLING_MPP"                        
 [352] "EO_BASO/MAST"                        "CMP"                                 "PRE_PRO_B"                          
 [355] "HSC_MPP"                             "MEMP"                                "DC3"                                
 [358] "CMP"                                 "GMP"                                 "PROMONOCYTE_(PROLIFERATING)"        
 [361] "EO_BASO/MAST"                        "CD16+_MACROPHAGE"                    "PROMONOCYTE"                        
 [364] "PRE_PRO_B"                           "BM_CD14_MONO"                        "CD14_MONO"                          
 [367] "OLFML3+_MICROGLIA"                   "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                   
 [370] "DC1"                                 "DC2"                                 "CD14_MONO"                          
 [373] "LMPP_ELP"                            "LMPP_ELP"                            "EO_BASO/MAST"                       
 [376] "KUPFFER_RP_MACROPHAGE"               "DC3"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [379] "DC1"                                 "HSC_MPP"                             "CD14+_MACROPHAGE"                   
 [382] "PRE_PRO_B"                           "CD14_MONO"                           "CD16+_MACROPHAGE"                   
 [385] "MEMP"                                "DC2"                                 "DC3"                                
 [388] "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD14+_MACROPHAGE"                   
 [391] "YS_MACROPHAGE"                       "PROLIFERATING_MACROPHAGE"            "CD16+_MACROPHAGE"                   
 [394] "DC2"                                 "CD14_MONO"                           "SPLENIC_MACROPHAGE"                 
 [397] "DC2"                                 "CD14_MONO"                           "PROMONOCYTE_(PROLIFERATING)"        
 [400] "CD16+_MACROPHAGE"                    "PROLIFERATING_MACROPHAGE"            "DC3"                                
 [403] "EO_BASO/MAST"                        "DC2"                                 "CD14_MONO"                          
 [406] "CD14+_MACROPHAGE"                    "DC3"                                 "OLFML3+_MICROGLIA"                  
 [409] "EO_BASO/MAST"                        "DC1"                                 "CD14+_MACROPHAGE"                   
 [412] "YS_MACROPHAGE"                       "DC2"                                 "PROLIFERATING_MACROPHAGE"           
 [415] "CD14_MONO"                           "CD16+_MACROPHAGE"                    "CYCLING_MPP"                        
 [418] "HSC_MPP"                             "EO_BASO/MAST"                        "CYCLING_MPP"                        
 [421] "CMP"                                 "EO_BASO/MAST"                        "HSC_MPP"                            
 [424] "MEMP"                                "KUPFFER_RP_MACROPHAGE"               "SPLENIC_MACROPHAGE"                 
 [427] "DC1"                                 "DC2"                                 "CD14_MONO"                          
 [430] "DC2"                                 "CD14+_MACROPHAGE"                    "CD16+_MACROPHAGE"                   
 [433] "DC3"                                 "CMP"                                 "EO_BASO/MAST"                       
 [436] "GMP"                                 "PRE_PRO_B"                           "PROMONOCYTE_(PROLIFERATING)"        
 [439] "CD16+_MACROPHAGE"                    "BM_CD14_MONO"                        "PROMONOCYTE"                        
 [442] "NEUTROPHIL"                          "KUPFFER_RP_MACROPHAGE"               "DC3"                                
 [445] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                       
 [448] "HSC_MPP"                             "MEMP"                                "DC2"                                
 [451] "CD14_MONO"                           "CYCLING_MPP"                         "CMP"                                
 [454] "CD16+_MACROPHAGE"                    "OLFML3+_MICROGLIA"                   "CD14+_MACROPHAGE"                   
 [457] "MEMP"                                "CD16+_MACROPHAGE"                    "DC1"                                
 [460] "DC2"                                 "CYCLING_MPP"                         "DC3"                                
 [463] "CMP"                                 "EO_BASO/MAST"                        "HSC_MPP"                            
 [466] "PRE_PRO_B"                           "MEMP"                                "CD14_MONO"                          
 [469] "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC3"                                
 [472] "CD16+_MACROPHAGE"                    "DC2"                                 "CD14_MONO"                          
 [475] "CMP"                                 "PROMONOCYTE_(PROLIFERATING)"         "DC3"                                
 [478] "GMP"                                 "PRE_PRO_B"                           "EO_BASO/MAST"                       
 [481] "CD16+_MACROPHAGE"                    "PROMONOCYTE"                         "BM_CD14_MONO"                       
 [484] "CD14_MONO"                           "NEUTROPHIL"                          "DC3"                                
 [487] "PROMONOCYTE_(PROLIFERATING)"         "PROLIFERATING_MACROPHAGE"            "DC2"                                
 [490] "DC1"                                 "CD14+_MACROPHAGE"                    "CD14_MONO"                          
 [493] "CD16+_MACROPHAGE"                    "BM_CD14_MONO"                        "DC1"                                
 [496] "CD16+_MACROPHAGE"                    "DC2"                                 "LMPP_ELP"                           
 [499] "EO_BASO/MAST"                        "DC3"                                 "CMP"                                
 [502] "PROMONOCYTE_(PROLIFERATING)"         "GMP"                                 "PRE_PRO_B"                          
 [505] "BM_CD14_MONO"                        "PROMONOCYTE"                         "CD14_MONO"                          
 [508] "DC1"                                 "CD16+_MACROPHAGE"                    "KUPFFER_RP_MACROPHAGE"              
 [511] "DC3"                                 "CD16+_MACROPHAGE"                    "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [514] "DC1"                                 "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [517] "DC2"                                 "CD14_MONO"                           "BM_CD14_MONO"                       
 [520] "MEMP"                                "EO_BASO/MAST"                        "PRE_PRO_B"                          
 [523] "EO_BASO/MAST"                        "MEMP"                                "KUPFFER_RP_MACROPHAGE"              
 [526] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD14_MONO"                           "CD16+_MACROPHAGE"                   
 [529] "DC2"                                 "DC1"                                 "CD16+_MACROPHAGE"                   
 [532] "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                   
 [535] "DC2"                                 "CD14_MONO"                           "KUPFFER_RP_MACROPHAGE"              
 [538] "DC3"                                 "CD16+_MACROPHAGE"                    "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [541] "CD14_MONO"                           "DC2"                                 "CD14+_MACROPHAGE"                   
 [544] "EO_BASO/MAST"                        "CMP"                                 "PRE_PRO_B"                          
 [547] "MEMP"                                "DC1"                                 "CD16+_MACROPHAGE"                   
 [550] "DC1"                                 "SPLENIC_MACROPHAGE"                  "KUPFFER_RP_MACROPHAGE"              
 [553] "CD16+_MACROPHAGE"                    "DC2"                                 "CD14_MONO"                          
 [556] "DC3"                                 "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                       
 [559] "DC1"                                 "OLFML3+_MICROGLIA"                   "CD14+_MACROPHAGE"                   
 [562] "DC2"                                 "PROLIFERATING_MACROPHAGE"            "CD14_MONO"                          
 [565] "DC1"                                 "DC2"                                 "DC1"                                
 [568] "SPLENIC_MACROPHAGE"                  "DC3"                                 "CD14+_MACROPHAGE"                   
 [571] "PROLIFERATING_MACROPHAGE"            "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [574] "YS_MACROPHAGE"                       "CD16+_MACROPHAGE"                    "DC2"                                
 [577] "EO_BASO/MAST"                        "CD14_MONO"                           "DC3"                                
 [580] "PRE_PRO_B"                           "CMP"                                 "GMP"                                
 [583] "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                        "HSC_MPP"                            
 [586] "CD16+_MACROPHAGE"                    "PROMONOCYTE"                         "BM_CD14_MONO"                       
 [589] "CD14_MONO"                           "NEUTROPHIL"                          "CD14+_MACROPHAGE"                   
 [592] "CD14_MONO"                           "CD16+_MACROPHAGE"                    "DC3"                                
 [595] "EO_BASO/MAST"                        "PROMONOCYTE_(PROLIFERATING)"         "PRE_PRO_B"                          
 [598] "CD16+_MACROPHAGE"                    "BM_CD14_MONO"                        "PROMONOCYTE"                        
 [601] "CD14_MONO"                           "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                   
 [604] "DC2"                                 "CD14+_MACROPHAGE"                    "PROLIFERATING_MACROPHAGE"           
 [607] "OLFML3+_MICROGLIA"                   "CD16+_MACROPHAGE"                    "DC3"                                
 [610] "PROLIFERATING_MACROPHAGE"            "SPLENIC_MACROPHAGE"                  "CD16+_MACROPHAGE"                   
 [613] "PROMONOCYTE_(PROLIFERATING)"         "CMP"                                 "PRE_PRO_B"                          
 [616] "DC1"                                 "DC2"                                 "GMP"                                
 [619] "CD14+_MACROPHAGE"                    "CD14_MONO"                           "EO_BASO/MAST"                       
 [622] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "BM_CD14_MONO"                        "KUPFFER_RP_MACROPHAGE"              
 [625] "DC3"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC1"                                
 [628] "PROLIFERATING_MACROPHAGE"            "CD16+_MACROPHAGE"                    "DC2"                                
 [631] "CD14_MONO"                           "SPLENIC_MACROPHAGE"                  "BM_CD14_MONO"                       
 [634] "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                    "PROMONOCYTE_(PROLIFERATING)"        
 [637] "EO_BASO/MAST"                        "CYCLING_MPP"                         "CMP"                                
 [640] "HSC_MPP"                             "CYCLING_MPP"                         "EO_BASO/MAST"                       
 [643] "HSC_MPP"                             "MEMP"                                "DC1"                                
 [646] "DC3"                                 "CYCLING_MPP"                         "CMP"                                
 [649] "EO_BASO/MAST"                        "HSC_MPP"                             "PRE_PRO_B"                          
 [652] "PROMONOCYTE_(PROLIFERATING)"         "BM_CD14_MONO"                        "DC1"                                
 [655] "CMP"                                 "DC3"                                 "CD16+_MACROPHAGE"                   
 [658] "PRE_PRO_B"                           "EO_BASO/MAST"                        "CMP"                                
 [661] "CD16+_MACROPHAGE"                    "MEMP"                                "EO_BASO/MAST"                       
 [664] "NEUTROPHIL"                          "CMP"                                 "DC3"                                
 [667] "CYCLING_MPP"                         "PROMONOCYTE_(PROLIFERATING)"         "GMP"                                
 [670] "PRE_PRO_B"                           "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                       
 [673] "PROMONOCYTE"                         "BM_CD14_MONO"                        "NEUTROPHIL"                         
 [676] "CD14_MONO"                           "CMP"                                 "MEMP"                               
 [679] "HSC_MPP"                             "EO_BASO/MAST"                        "KUPFFER_RP_MACROPHAGE"              
 [682] "DC3"                                 "DC2"                                 "DC1"                                
 [685] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                    "CD14_MONO"                          
 [688] "DC3"                                 "PROLIFERATING_MACROPHAGE"            "CD14+_MACROPHAGE"                   
 [691] "CD14_MONO"                           "DC2"                                 "CD16+_MACROPHAGE"                   
 [694] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [697] "DC3"                                 "PROLIFERATING_MACROPHAGE"            "DC1"                                
 [700] "PRE_PRO_B"                           "SPLENIC_MACROPHAGE"                  "EO_BASO/MAST"                       
 [703] "DC2"                                 "CD16+_MACROPHAGE"                    "CD14_MONO"                          
 [706] "KUPFFER_RP_MACROPHAGE"               "CYCLING_MPP"                         "DC3"                                
 [709] "PROLIFERATING_MACROPHAGE"            "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "KUPFFER_RP_MACROPHAGE"              
 [712] "CD16+_MACROPHAGE"                    "CD14+_MACROPHAGE"                    "CMP"                                
 [715] "CYCLING_MPP"                         "EO_BASO/MAST"                        "GMP"                                
 [718] "HSC_MPP"                             "MEMP"                                "CD14_MONO"                          
 [721] "KUPFFER_RP_MACROPHAGE"               "CYCLING_MPP"                         "CMP"                                
 [724] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "PRE_PRO_B"                           "HSC_MPP"                            
 [727] "DC3"                                 "EO_BASO/MAST"                        "CD16+_MACROPHAGE"                   
 [730] "MEMP"                                "DC3"                                 "OLFML3+_MICROGLIA"                  
 [733] "PROLIFERATING_MACROPHAGE"            "DC1"                                 "YS_MACROPHAGE"                      
 [736] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [739] "DC2"                                 "LMPP_ELP"                            "CD14_MONO"                          
 [742] "DC3"                                 "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                       
 [745] "BM_CD14_MONO"                        "PROMONOCYTE"                         "SPLENIC_MACROPHAGE"                 
 [748] "DC1"                                 "KUPFFER_RP_MACROPHAGE"               "DC3"                                
 [751] "DC2"                                 "CD14_MONO"                           "CMP"                                
 [754] "EO_BASO/MAST"                        "GMP"                                 "PROMONOCYTE_(PROLIFERATING)"        
 [757] "PROMONOCYTE"                         "PROMONOCYTE"                         "DC3"                                
 [760] "CMP"                                 "GMP"                                 "PRE_PRO_B"                          
 [763] "CD16+_MACROPHAGE"                    "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                       
 [766] "BM_CD14_MONO"                        "NEUTROPHIL"                          "CD14_MONO"                          
 [769] "DC1"                                 "DC3"                                 "KUPFFER_RP_MACROPHAGE"              
 [772] "PROMONOCYTE_(PROLIFERATING)"         "PRE_PRO_B"                           "CD16+_MACROPHAGE"                   
 [775] "EO_BASO/MAST"                        "BM_CD14_MONO"                        "CD14+_MACROPHAGE"                   
 [778] "CD14_MONO"                           "PROMONOCYTE"                         "GMP"                                
 [781] "KUPFFER_RP_MACROPHAGE"               "CMP"                                 "DC3"                                
 [784] "EO_BASO/MAST"                        "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "DC2"                                
 [787] "PROMONOCYTE_(PROLIFERATING)"         "CD14_MONO"                           "CD16+_MACROPHAGE"                   
 [790] "BM_CD14_MONO"                        "GMP"                                 "PRE_PRO_B"                          
 [793] "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                        "CMP"                                
 [796] "PROMONOCYTE"                         "BM_CD14_MONO"                        "CD14_MONO"                          
 [799] "DC3"                                 "DC1"                                 "CD14+_MACROPHAGE"                   
 [802] "PROLIFERATING_MACROPHAGE"            "DC2"                                 "CD16+_MACROPHAGE"                   
 [805] "EO_BASO/MAST"                        "CD14_MONO"                           "BM_CD14_MONO"                       
 [808] "DC1"                                 "DC3"                                 "OLFML3+_MICROGLIA"                  
 [811] "DC2"                                 "CD14+_MACROPHAGE"                    "YS_MACROPHAGE"                      
 [814] "PROLIFERATING_MACROPHAGE"            "CD14_MONO"                           "CD16+_MACROPHAGE"                   
 [817] "EO_BASO/MAST"                        "BM_CD14_MONO"                        "DC1"                                
 [820] "OLFML3+_MICROGLIA"                   "DC3"                                 "DC1"                                
 [823] "PROLIFERATING_MACROPHAGE"            "CD14_MONO"                           "DC2"                                
 [826] "CD14+_MACROPHAGE"                    "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [829] "DC3"                                 "PROMONOCYTE_(PROLIFERATING)"         "PRE_PRO_B"                          
 [832] "PROLIFERATING_MACROPHAGE"            "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                       
 [835] "BM_CD14_MONO"                        "PROMONOCYTE"                         "GMP"                                
 [838] "CD14+_MACROPHAGE"                    "CD14_MONO"                           "EO_BASO/MAST"                       
 [841] "CYCLING_MPP"                         "CMP"                                 "HSC_MPP"                            
 [844] "GMP"                                 "PRE_PRO_B"                           "PROLIFERATING_MACROPHAGE"           
 [847] "CD14+_MACROPHAGE"                    "YS_MACROPHAGE"                       "DC2"                                
 [850] "OLFML3+_MICROGLIA"                   "CD16+_MACROPHAGE"                    "CD14_MONO"                          
 [853] "KUPFFER_RP_MACROPHAGE"               "DC3"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [856] "DC1"                                 "DC2"                                 "CD16+_MACROPHAGE"                   
 [859] "CD14+_MACROPHAGE"                    "PROLIFERATING_MACROPHAGE"            "SPLENIC_MACROPHAGE"                 
 [862] "CD14_MONO"                           "EO_BASO/MAST"                        "CYCLING_MPP"                        
 [865] "CMP"                                 "HSC_MPP"                             "CMP"                                
 [868] "DC3"                                 "DC2"                                 "KUPFFER_RP_MACROPHAGE"              
 [871] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "CD16+_MACROPHAGE"                    "DC1"                                
 [874] "CD14_MONO"                           "CD14+_MACROPHAGE"                    "DC3"                                
 [877] "KUPFFER_RP_MACROPHAGE"               "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "EO_BASO/MAST"                       
 [880] "PRE_PRO_B"                           "DC1"                                 "CD16+_MACROPHAGE"                   
 [883] "HSC_MPP"                             "SPLENIC_MACROPHAGE"                  "MEMP"                               
 [886] "CD14_MONO"                           "DC2"                                 "CD14+_MACROPHAGE"                   
 [889] "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                        "CYCLING_MPP"                        
 [892] "DC3"                                 "CMP"                                 "HSC_MPP"                            
 [895] "DC3"                                 "CYCLING_MPP"                         "CMP"                                
 [898] "PROMONOCYTE_(PROLIFERATING)"         "EO_BASO/MAST"                        "GMP"                                
 [901] "PROMONOCYTE"                         "PRE_PRO_B"                           "HSC_MPP"                            
 [904] "BM_CD14_MONO"                        "CD14_MONO"                           "DC1"                                
 [907] "CYCLING_MPP"                         "CMP"                                 "HSC_MPP"                            
 [910] "EO_BASO/MAST"                        "MEMP"                                "PRE_PRO_B"                          
 [913] "MEMP"                                "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [916] "DC1"                                 "KUPFFER_RP_MACROPHAGE"               "DC2"                                
 [919] "PROLIFERATING_KUPFFER_RP_MACROPHAGE" "YS_MACROPHAGE"                       "PROLIFERATING_MACROPHAGE"           
 [922] "SPLENIC_MACROPHAGE"                  "CD16+_MACROPHAGE"                    "CD14_MONO"                          
 [925] "EO_BASO/MAST"                        "DC3"                                 "KUPFFER_RP_MACROPHAGE"              
 [928] "SPLENIC_MACROPHAGE"                  "DC1"                                 "PROLIFERATING_MACROPHAGE"           
 [931] "CD14_MONO"                           "PRE_PRO_B"                           "DC2"                                
 [934] "CD16+_MACROPHAGE"                    "EO_BASO/MAST"                        "CD14+_MACROPHAGE"                   
 [937] "DC1"                                 "CD16+_MACROPHAGE"                    "DC1"                                
 [940] "DC3"                                 "PROLIFERATING_MACROPHAGE"            "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [943] "YS_MACROPHAGE"                       "CD16+_MACROPHAGE"                    "DC2"                                
 [946] "KUPFFER_RP_MACROPHAGE"               "CD14+_MACROPHAGE"                    "DC3"                                
 [949] "CMP"                                 "EO_BASO/MAST"                        "PROMONOCYTE_(PROLIFERATING)"        
 [952] "GMP"                                 "CD16+_MACROPHAGE"                    "PRE_PRO_B"                          
 [955] "PROMONOCYTE"                         "CD14_MONO"                           "BM_CD14_MONO"                       
 [958] "PROLIFERATING_MACROPHAGE"            "DC3"                                 "CD14+_MACROPHAGE"                   
 [961] "OLFML3+_MICROGLIA"                   "KUPFFER_RP_MACROPHAGE"               "CD16+_MACROPHAGE"                   
 [964] "CD14_MONO"                           "OLFML3+_MICROGLIA"                   "DC3"                                
 [967] "DC1"                                 "DC2"                                 "PROLIFERATING_MACROPHAGE"           
 [970] "CD14+_MACROPHAGE"                    "CD14_MONO"                           "CD16+_MACROPHAGE"                   
 [973] "KUPFFER_RP_MACROPHAGE"               "DC3"                                 "PROLIFERATING_KUPFFER_RP_MACROPHAGE"
 [976] "CD14_MONO"                           "CD16+_MACROPHAGE"                    "DC2"                                
 [979] "EO_BASO/MAST"                        "CMP"                                 "HSC_MPP"                            
 [982] "CYCLING_MPP"                         "DC3"                                 "PRE_PRO_B"                          
 [985] "GMP"                                 "PROMONOCYTE"                         "DC2"                                
 [988] "KUPFFER_RP_MACROPHAGE"               "CD14_MONO"                           "PROMONOCYTE_(PROLIFERATING)"        
 [991] "CD16+_MACROPHAGE"                    "BM_CD14_MONO"                        "CMP"                                
 [994] "PRE_PRO_B"                           "PROMONOCYTE_(PROLIFERATING)"         "GMP"                                
 [997] "PROMONOCYTE"                         "EO_BASO/MAST"                        "BM_CD14_MONO"                       
[1000] "NEUTROPHIL"                         
 [ reached getOption("max.print") -- omitted 30 entries ]
object <- mofa_obj

Prepare 4 training


data_opts <- get_default_data_options(object)
data_opts$use_float32 <- TRUE
data_opts$center_groups <- FALSE
object@data_options <- data_opts

model_opts <- get_default_model_options(object)
model_opts$num_factors <- 30
# model_opts$ard_factors <- FALSE

train_opts <- get_default_training_options(object)
train_opts$seed <- 2020
train_opts$convergence_mode <- "medium" # use "fast" for faster training
train_opts$stochastic <- FALSE

# mefisto_opts <- get_default_mefisto_options(object)
# mefisto_opts$warping <- FALSE
# mefisto_opts$sparseGP <- TRUE

object <- prepare_mofa(
  object = object,
  data_options = data_opts,
  model_options = model_opts,
  training_options = train_opts
) 

# Multi-group mode requested.

This is an advanced option, if this is the first time that you are running MOFA, we suggest that you try do some exploration first without specifying groups. Two important remarks:

 - The aim of the multi-group framework is to identify the sources of variability *within* the groups. If your aim is to find a factor that 'separates' the groups, you DO NOT want to use the multi-group framework. Please see the FAQ on the MOFA2 webpage.

 - It is important to account for the group effect before selecting highly variable features (HVFs). We suggest that either you calculate HVFs per group and then take the union, or regress out the group effect before HVF selection
Checking data options...
Due to string size limitations in the HDF5 format, sample names will be trimmed to less than 50 charactersChecking training options...
Checking model options...
object
Untrained MOFA model with the following characteristics: 
 Number of views: 1 
 Views names: corrected_logcounts 
 Number of features (per view): 6726 
 Number of groups: 24 
 Groups names: BM_CD14_MONO CD14_MONO CD14+_MACROPHAGE CD16+_MACROPHAGE CMP CYCLING_MPP DC1 DC2 DC3 EO_BASO_MAST GMP HSC_MPP KUPFFER_RP_MACROPHAGE LMPP_ELP MEMP NEUTROPHIL OLFML3+_MICROGLIA PRE_PRO_B PROLIFERATING_KUPFFER_RP_MACROPHAGE PROLIFERATING_MACROPHAGE PROMONOCYTE PROMONOCYTE_(PROLIFERATING) SPLENIC_MACROPHAGE YS_MACROPHAGE 
 Number of samples (per group): 35 86 71 89 44 29 60 76 80 86 25 32 47 10 26 10 20 37 36 48 20 33 18 12 
 

Train

Wrapped in run_mofa.R

mofa_trained
Error: object 'mofa_trained' not found
### Tweaking the MOFA2 loading function because the quality control complains
load_model <- function(file, sort_factors = TRUE, on_disk = FALSE, load_data = TRUE,
                       remove_outliers = FALSE, remove_inactive_factors = TRUE, verbose = FALSE,
                       load_interpol_Z = FALSE) {

  # Create new MOFAodel object
  object <- new("MOFA")
  object@status <- "trained"
  
  # Set on_disk option
  if (on_disk) { 
    object@on_disk <- TRUE 
  } else { 
      object@on_disk <- FALSE 
  }
  
  # Get groups and data set names from the hdf5 file object
  h5ls.out <- h5ls(file, datasetinfo = FALSE)
  
  ########################
  ## Load training data ##
  ########################

  # Load names
  if ("views" %in% h5ls.out$name) {
    view_names <- as.character( h5read(file, "views")[[1]] )
    group_names <- as.character( h5read(file, "groups")[[1]] )
    feature_names <- h5read(file, "features")[view_names]
    sample_names  <- h5read(file, "samples")[group_names] 
  } else {  # for old models
    feature_names <- h5read(file, "features")
    sample_names  <- h5read(file, "samples")
    view_names <- names(feature_names)
    group_names <- names(sample_names)
    h5ls.out <- h5ls.out[grep("variance_explained", h5ls.out$name, invert = TRUE),]
  }
  if("covariates" %in%  h5ls.out$name){
    covariate_names <- as.character( h5read(file, "covariates")[[1]])
  } else {
    covariate_names <- NULL
  }

  # Load training data (as nested list of matrices)
  data <- list(); intercepts <- list()
  if (load_data && "data"%in%h5ls.out$name) {
    
    object@data_options[["loaded"]] <- TRUE
    if (verbose) message("Loading data...")
    
    for (m in view_names) {
      data[[m]] <- list()
      intercepts[[m]] <- list()
      for (g in group_names) {
        if (on_disk) {
          # as DelayedArrays
          data[[m]][[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("data/%s/%s", m, g) ) )
        } else {
          # as matrices
          data[[m]][[g]] <- h5read(file, sprintf("data/%s/%s", m, g) )
          tryCatch(intercepts[[m]][[g]] <- as.numeric( h5read(file, sprintf("intercepts/%s/%s", m, g) ) ), error = function(e) { NULL })
        }
        # Replace NaN by NA
        data[[m]][[g]][is.nan(data[[m]][[g]])] <- NA # this realised into memory, TO FIX
      }
    }
    
  # Create empty training data (as nested list of empty matrices, with the correct dimensions)
  } else {
    
    object@data_options[["loaded"]] <- FALSE
    
    for (m in view_names) {
      data[[m]] <- list()
      for (g in group_names) {
        data[[m]][[g]] <- .create_matrix_placeholder(rownames = feature_names[[m]], colnames = sample_names[[g]])
      }
    }
  }

  object@data <- data
  object@intercepts <- intercepts


  # Load metadata if any
  if ("samples_metadata" %in% h5ls.out$name) {
    object@samples_metadata <- bind_rows(lapply(group_names, function(g) as.data.frame(h5read(file, sprintf("samples_metadata/%s", g)))))
  }
  if ("features_metadata" %in% h5ls.out$name) {
    object@features_metadata <- bind_rows(lapply(view_names, function(m) as.data.frame(h5read(file, sprintf("features_metadata/%s", m)))))
  }
  
  # ############################
  # ## Load sample covariates ##
  # ############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))){
  #   covariates <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates[[g]] <- h5read(file, sprintf("cov_samples/%s", g) )
  #     }    
  #   }
  # } else covariates <- NULL
  # object@covariates <- covariates

  # if (any(grepl("cov_samples_transformed", h5ls.out$group))){
  #   covariates_warped <- list()
  #   for (g in group_names) {
  #     if (on_disk) {
  #       # as DelayedArrays
  #       covariates_warped[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("cov_samples_transformed/%s", g) ) )
  #     } else {
  #       # as matrices
  #       covariates_warped[[g]] <- h5read(file, sprintf("cov_samples_transformed/%s", g) )
  #     }    
  #   }
  # } else covariates_warped <- NULL
  # object@covariates_warped <- covariates_warped
  
  # #######################
  # ## Load interpolated factor values ##
  # #######################
  # 
  # interpolated_Z <- list()
  # if (isTRUE(load_interpol_Z)) {
  #   
  #   if (isTRUE(verbose)) message("Loading interpolated factor values...")
  #   
  #   for (g in group_names) {
  #     interpolated_Z[[g]] <- list()
  #     if (on_disk) {
  #       # as DelayedArrays
  #       # interpolated_Z[[g]] <- DelayedArray::DelayedArray( HDF5ArraySeed(file, name = sprintf("Z_predictions/%s", g) ) )
  #     } else {
  #       # as matrices
  #       tryCatch( {
  #         interpolated_Z[[g]][["mean"]] <- h5read(file, sprintf("Z_predictions/%s/mean", g) )
  #       }, error = function(x) { print("Predicitions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["variance"]] <- h5read(file, sprintf("Z_predictions/%s/variance", g) )
  #       }, error = function(x) { print("Variance of predictions of Z not found, not loading it...") })
  #       tryCatch( {
  #         interpolated_Z[[g]][["new_values"]] <- h5read(file, "Z_predictions/new_values")
  #       }, error = function(x) { print("New values of Z not found, not loading it...") })
  #     }
  #   }
  # }
  # object@interpolated_Z <- interpolated_Z
  
  #######################
  ## Load expectations ##
  #######################

  expectations <- list()
  node_names <- h5ls.out[h5ls.out$group=="/expectations","name"]

  if (verbose) message(paste0("Loading expectations for ", length(node_names), " nodes..."))

  if ("AlphaW" %in% node_names)
    expectations[["AlphaW"]] <- h5read(file, "expectations/AlphaW")[view_names]
  if ("AlphaZ" %in% node_names)
    expectations[["AlphaZ"]] <- h5read(file, "expectations/AlphaZ")[group_names]
  if ("Sigma" %in% node_names)
    expectations[["Sigma"]] <- h5read(file, "expectations/Sigma")
  if ("Z" %in% node_names)
    expectations[["Z"]] <- h5read(file, "expectations/Z")[group_names]
  if ("W" %in% node_names)
    expectations[["W"]] <- h5read(file, "expectations/W")[view_names]
  if ("ThetaW" %in% node_names)
    expectations[["ThetaW"]] <- h5read(file, "expectations/ThetaW")[view_names]
  if ("ThetaZ" %in% node_names)
    expectations[["ThetaZ"]] <- h5read(file, "expectations/ThetaZ")[group_names]
  # if ("Tau" %in% node_names)
  #   expectations[["Tau"]] <- h5read(file, "expectations/Tau")
  
  object@expectations <- expectations

  
  ########################
  ## Load model options ##
  ########################

  if (verbose) message("Loading model options...")

  tryCatch( {
    object@model_options <- as.list(h5read(file, 'model_options', read.attributes = TRUE))
  }, error = function(x) { print("Model options not found, not loading it...") })

  # Convert True/False strings to logical values
  for (i in names(object@model_options)) {
    if (object@model_options[i] == "False" || object@model_options[i] == "True") {
      object@model_options[i] <- as.logical(object@model_options[i])
    } else {
      object@model_options[i] <- object@model_options[i]
    }
  }

  ##########################################
  ## Load training options and statistics ##
  ##########################################

  if (verbose) message("Loading training options and statistics...")

  # Load training options
  if (length(object@training_options) == 0) {
    tryCatch( {
      object@training_options <- as.list(h5read(file, 'training_opts', read.attributes = TRUE))
    }, error = function(x) { print("Training opts not found, not loading it...") })
  }

  # Load training statistics
  tryCatch( {
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
    object@training_stats <- h5read(file, 'training_stats', read.attributes = TRUE)
  }, error = function(x) { print("Training stats not found, not loading it...") })

  #############################
  ## Load covariates options ##
  #############################
  # 
  # if (any(grepl("cov_samples", h5ls.out$group))) { 
  #   if (isTRUE(verbose)) message("Loading covariates options...")
  #   tryCatch( {
  #     object@mefisto_options <- as.list(h5read(file, 'smooth_opts', read.attributes = TRUE))
  #   }, error = function(x) { print("Covariates options not found, not loading it...") })
  #   
  #   # Convert True/False strings to logical values
  #   for (i in names(object@mefisto_options)) {
  #     if (object@mefisto_options[i] == "False" | object@mefisto_options[i] == "True") {
  #       object@mefisto_options[i] <- as.logical(object@mefisto_options[i])
  #     } else {
  #       object@mefisto_options[i] <- object@mefisto_options[i]
  #     }
  #   }
  #   
  # }
  # 
  
    
  #######################################
  ## Load variance explained estimates ##
  #######################################
  
  if ("variance_explained" %in% h5ls.out$name) {
    r2_list <- list(
      r2_total = h5read(file, "variance_explained/r2_total")[group_names],
      r2_per_factor = h5read(file, "variance_explained/r2_per_factor")[group_names]
    )
    object@cache[["variance_explained"]] <- r2_list
  }
  
  # Hack to fix the problems where variance explained values range from 0 to 1 (%)
  if (max(sapply(object@cache$variance_explained$r2_total,max,na.rm=TRUE),na.rm=TRUE)<1) {
    for (m in 1:length(view_names)) {
      for (g in 1:length(group_names)) {
        object@cache$variance_explained$r2_total[[g]][[m]] <- 100 * object@cache$variance_explained$r2_total[[g]][[m]]
        object@cache$variance_explained$r2_per_factor[[g]][,m] <- 100 * object@cache$variance_explained$r2_per_factor[[g]][,m]
      }
    }
  }
  
  ##############################
  ## Specify dimensionalities ##
  ##############################
  
  # Specify dimensionality of the data
  object@dimensions[["M"]] <- length(data)                            # number of views
  object@dimensions[["G"]] <- length(data[[1]])                       # number of groups
  object@dimensions[["N"]] <- sapply(data[[1]], ncol)                 # number of samples (per group)
  object@dimensions[["D"]] <- sapply(data, function(e) nrow(e[[1]]))  # number of features (per view)
  # object@dimensions[["C"]] <- nrow(covariates[[1]])                        # number of covariates
  object@dimensions[["K"]] <- ncol(object@expectations$Z[[1]])        # number of factors
  
  # Assign sample and feature names (slow for large matrices)
  if (verbose) message("Assigning names to the different dimensions...")

  # Create default features names if they are null
  if (is.null(feature_names)) {
    print("Features names not found, generating default: feature1_view1, ..., featureD_viewM")
    feature_names <- lapply(seq_len(object@dimensions[["M"]]),
                            function(m) sprintf("feature%d_view_&d", as.character(seq_len(object@dimensions[["D"]][m])), m))
  } else {
    # Check duplicated features names
    all_names <- unname(unlist(feature_names))
    duplicated_names <- unique(all_names[duplicated(all_names)])
    if (length(duplicated_names)>0) 
      warning("There are duplicated features names across different views. We will add the suffix *_view* only for those features 
            Example: if you have both TP53 in mRNA and mutation data it will be renamed to TP53_mRNA, TP53_mutation")
    for (m in names(feature_names)) {
      tmp <- which(feature_names[[m]] %in% duplicated_names)
      if (length(tmp)>0) feature_names[[m]][tmp] <- paste(feature_names[[m]][tmp], m, sep="_")
    }
  }
  features_names(object) <- feature_names
  
  # Create default samples names if they are null
  if (is.null(sample_names)) {
    print("Samples names not found, generating default: sample1, ..., sampleN")
    sample_names <- lapply(object@dimensions[["N"]], function(n) paste0("sample", as.character(seq_len(n))))
  }
  samples_names(object) <- sample_names

  # Add covariates names
  # if(!is.null(object@covariates)){
  #   # Create default covariates names if they are null
  #   if (is.null(covariate_names)) {
  #     print("Covariate names not found, generating default: covariate1, ..., covariateC")
  #     covariate_names <- paste0("sample", as.character(seq_len(object@dimensions[["C"]])))
  #   }
  #   covariates_names(object) <- covariate_names
  # }
  
  # Set views names
  if (is.null(names(object@data))) {
    print("Views names not found, generating default: view1, ..., viewM")
    view_names <- paste0("view", as.character(seq_len(object@dimensions[["M"]])))
  }
  views_names(object) <- view_names
  
  # Set groups names
  if (is.null(names(object@data[[1]]))) {
    print("Groups names not found, generating default: group1, ..., groupG")
    group_names <- paste0("group", as.character(seq_len(object@dimensions[["G"]])))
  }
  groups_names(object) <- group_names
  
  # Set factors names
  factors_names(object)  <- paste0("Factor", as.character(seq_len(object@dimensions[["K"]])))
  
  ###################
  ## Parse factors ##
  ###################
  
  # Calculate variance explained estimates per factor
  if (is.null(object@cache[["variance_explained"]])) {
    object@cache[["variance_explained"]] <- calculate_variance_explained(object)
  } 
  
  # Remove inactive factors
  if (remove_inactive_factors) {
    r2 <- rowSums(do.call('cbind', lapply(object@cache[["variance_explained"]]$r2_per_factor, rowSums, na.rm=TRUE)))
    var.threshold <- 0.0001
    if (all(r2 < var.threshold)) {
      warning(sprintf("All %s factors were found to explain little or no variance so remove_inactive_factors option has been disabled.", length(r2)))
    } else if (any(r2 < var.threshold)) {
      object <- subset_factors(object, which(r2>=var.threshold))
      message(sprintf("%s factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)", sum(r2 < var.threshold)))
    }
  }
  
  # [Done in mofapy2] Sort factors by total variance explained
  if (sort_factors && object@dimensions$K>1) {

    # Sanity checks
    if (verbose) message("Re-ordering factors by their variance explained...")

    # Calculate variance explained per factor across all views
    r2 <- rowSums(sapply(object@cache[["variance_explained"]]$r2_per_factor, function(e) rowSums(e, na.rm = TRUE)))
    order_factors <- c(names(r2)[order(r2, decreasing = TRUE)])

    # re-order factors
    object <- subset_factors(object, order_factors)
  }

  # Mask outliers
  if (remove_outliers) {
    if (verbose) message("Removing outliers...")
    object <- .detect_outliers(object)
  }
  
  # Mask intercepts for non-Gaussian data
  if (any(object@model_options$likelihoods!="gaussian")) {
    for (m in names(which(object@model_options$likelihoods!="gaussian"))) {
      for (g in names(object@intercepts[[m]])) {
        object@intercepts[[m]][[g]] <- NA
      }
    }
  }

  # ######################
  # ## Quality controls ##
  # ######################
  # 
  # if (verbose) message("Doing quality control...")
  # object <- .quality_control(object, verbose = verbose)
  # 
  return(object)
}

mofa_trained <- load_model(outfile)
Recalculating total variance explained values (r2_total)...
9 factors were found to explain no variance and they were removed for downstream analysis. You can disable this option by setting load_model(..., remove_inactive_factors = FALSE)
samples_names(mofa_trained) <- samples_names(object)
samples_metadata(mofa_trained)
rownames(samples_metadata(mofa_trained)) <- samples_metadata(mofa_trained)[["sample"]]

Prune factors

Visualize variance explained by factors

plot_factor_cor(mofa_trained, method = "spearman")

Identify technical factors

Do factors relate to the number of cells in pseudobulk?

Distinguish technical factors by weight sparsity

exclude_factors
[1] Factor1  Factor2  Factor11 Factor13 Factor18 Factor20
21 Levels: Factor1 Factor2 Factor3 Factor4 Factor5 Factor6 Factor7 Factor8 Factor9 Factor10 ... Factor21

Find factors that separate pseudobulks by tissue

Calculating Adjusted Mutual Information between organ identity and clustering of pseudobulks based on factor values

Factor ID plots

plot_factor_ordered <- function(mofa_trained, f){
  factor_df <- get_factors(mofa_trained, factors = f, as.data.frame = TRUE) %>%
      mutate(organ = sapply(str_split(sample, "_"), function(x) x[2])) %>%
      group_by(group) %>%
      mutate(gr_mean = median(value)) %>%
      ungroup() %>%
      arrange(gr_mean) %>%
      mutate(group=factor(group, levels=unique(group))) 
  
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) %>%
    mutate(group=factor(group, levels = levels(factor_df$group)))
  
  pl1 <- factor_df %>%
      ggplot(aes(group, value)) +
      geom_boxplot() +
      geom_jitter(aes(color= organ), size=0.7) +
      geom_hline(yintercept = 0, linetype=2) +
      coord_flip() +
      ylab(paste0("Factor ", f)) +
      theme_bw(base_size = 14)
  
  pl2 <- r2_df %>%
    ggplot(aes(group, value)) +
    geom_col() +
    coord_flip() +
    ylab("% variance explained") +
    theme_bw(base_size = 14) +
    remove_y_axis()
  
  pl1 + pl2 + plot_layout(widths=c(2,1), guides="collect") 
}

get_top_celltype_per_factor <- function(mofa_trained, f){
  r2_df <- get_variance_explained(mofa_trained, factors = f, as.data.frame = TRUE)[[1]] %>%
    filter(factor==paste0('Factor',f)) 
    # mutate(group=factor(group, levels = ))
  top_quant_r2 <- quantile(r2_df$value, probs = seq(0, 1, by = 0.2))["80%"]
  top_groups <- r2_df$group[r2_df$value >= top_quant_r2]
  return(top_groups)
}

save_factor_id <- function(mofa_trained, f, figdir){
  ## Order celltypes by factor values
  p1 <- plot_factor_ordered(mofa_trained, f)
  
  ## Plot factor values across organs for celltypes with high variance explained
  p2 <- plot_factor(mofa_trained, factors = f, groups = get_top_celltype_per_factor(mofa_trained, f), group_by = "group", 
              color_by = "organ", 
              dot_size = 2, dodge = TRUE
              )
  
  ## Plot factor weights on genes
  # plot_data_heatmap(mofa_trained, factor = f, nfeatures = 50, text_size = 3, show_colnames=FALSE,
  #                   annotation_samples = c("organ", "time", "method", "donor"))
  p3 <- plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
   scale_y_discrete(expand=c(0.1, 0.1))
  
  full_pl <- (p1 | (p2 / p3)) +
    plot_layout(guides="collect") 
  ggsave(glue("{figdir}/MOFA_{split}_factorID_factor{f}.pdf"), plot=full_pl, width = 15, height = 10)
}

for (f in 1:mofa_trained@dimensions$K){
  print(paste0("Saving ID for Factor ", f, "..."))
  save_factor_id(mofa_trained, f=f, figdir = figdir)  
}
[1] "Saving ID for Factor 1..."
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
[1] "Saving ID for Factor 2..."
[1] "Saving ID for Factor 3..."
[1] "Saving ID for Factor 4..."
[1] "Saving ID for Factor 5..."
[1] "Saving ID for Factor 6..."
[1] "Saving ID for Factor 7..."
[1] "Saving ID for Factor 8..."
[1] "Saving ID for Factor 9..."
[1] "Saving ID for Factor 10..."
[1] "Saving ID for Factor 11..."
[1] "Saving ID for Factor 12..."
[1] "Saving ID for Factor 13..."
[1] "Saving ID for Factor 14..."
[1] "Saving ID for Factor 15..."
[1] "Saving ID for Factor 16..."
[1] "Saving ID for Factor 17..."
[1] "Saving ID for Factor 18..."
[1] "Saving ID for Factor 19..."
[1] "Saving ID for Factor 20..."
[1] "Saving ID for Factor 21..."
# save_factor_id(mofa_trained, f=1, figdir = figdir)  
# plot_weights(mofa_trained, factors = f, nfeatures = 30, text_size = 3) +
#    scale_y_discrete(expand=c(0.1, 0.1))

Expression of top R2 factors

get_top_weight_genes <- function(mofa_trained, f, n_top=20, which="top"){
  w_df <- get_weights(mofa_trained, factors = f, as.data.frame = TRUE) %>%
    dplyr::arrange(value) 
  top_genes <- w_df %>%
      dplyr::top_n(n_top, value) %>%
      dplyr::pull(feature) %>%
      as.character()
  bot_genes <-  w_df %>%
      dplyr::top_n(n_top, -value) %>%
      dplyr::pull(feature) %>%
      as.character()
  if (which=="top") {
    genes <- top_genes
  } else if (which=="bottom"){
    genes <- bot_genes
  } else if (which=="both"){
    genes <- c(top_genes, bot_genes)
  }
  return(genes)
}

plot_data_top_weights <- function(mofa_trained, ct, f, n_top=20, which="top"){
  genes <- get_top_weight_genes(mofa_trained, f, which=which, n_top=n_top)
  data <- get_data(mofa_trained, groups=ct)[[1]][[1]][genes,]
  
  pl_df <- reshape2::melt(data, varnames=c("gene", "sample")) %>%
    dplyr::left_join(samples_metadata(mofa_trained)) %>%
    dplyr::arrange(age) %>%
    dplyr::mutate(sample=factor(sample, levels=unique(sample))) %>%
    dplyr::group_by(gene) %>%
    dplyr::mutate(value=scale(value))
  pl_df %>%
    ggplot(aes(sample, gene, fill=value)) +
    geom_tile() +
    facet_grid(.~organ, space="free", scales="free") +
    scale_fill_gradient2(high="red", low="blue", name="Scaled\nexpression") +
    xlab("----age--->") + ylab(glue("{which} weight genes")) +
    theme_bw(base_size=16) +
    theme(axis.ticks.x = element_blank(), axis.text.x = element_blank()) +
    ggtitle(glue('{ct} - {f}'))
}

for (g in all_groups){
  fs <- get_top_factor_per_celltype(mofa_trained, g, min_R2=2)
  fs <- fs[!fs %in% exclude_factors]
  if (length(fs) > 0){
    top_plots <- lapply(fs, function(x) (plot_data_top_weights(mofa_trained, g, x, which="top") + remove_x_axis()) /  
                          plot_data_top_weights(mofa_trained, g, x, which="bottom") + ggtitle("")
    )
    full_pl <-wrap_plots(top_plots, ncol=1) 
    ggsave(glue("{figdir}/top_factors_expr_{g}.pdf"),plot=full_pl,  width=12, height = 10*length(top_plots))
    }  
}
get_variance_explained(mofa_trained, factors = 5, as.data.frame = TRUE)[[1]] %>%
  dplyr::filter(factor=="Factor5") %>%
  dplyr::arrange(value) %>%
  dplyr::mutate(group=factor(group, levels=unique(group))) %>%
  # dplyr::filter(value > 1) %>%
  ggplot(aes(group, value)) +
  geom_col() +
  coord_flip() +
  geom_hline(yintercept = 2, color="red", linetype=2) +
  ylab("% Var. explained")

get_variance_explained(mofa_trained, factors = 5, as.data.frame = TRUE)[[1]] %>%
  dplyr::filter(group=="CD16+_MACROPHAGE") %>%
  dplyr::filter(!factor %in% exclude_factors) %>%
  dplyr::arrange(value) %>%
  dplyr::mutate(factor=factor(factor, levels=unique(factor))) %>%
  # dplyr::filter(value > 1) %>%
  ggplot(aes(factor, value)) +
  geom_col() +
  coord_flip() +
  geom_hline(yintercept = 2, color="red", linetype=2) +
  ylab("% Var. explained")


# 
# gs <- dplyr::filter(gr_top_factors, top_factors=="Factor4") %>% dplyr::pull(group)
# groups = gs[!gs %in% c("EO_BASO_MAST", "PRE_PRO_B", "DC2")]
# 
# plot_data_heatmap(mofa_trained, factor = 4, groups = c("NEUTROPHIL"), show_colnames=FALSE, annotation_samples = 'organ', annotation_colors=list(organ=org_colors), features = 50)

GSEA

# BiocManager::install("MOFAdata")
library(MOFAdata)
utils::data(reactomeGS)
head(rownames(reactomeGS))

## Remove row with NA
reactomeGS <- reactomeGS[!is.na(rownames(reactomeGS)),]
BiocManager::install('ensembldb')
library(EnsDb.Hsapiens.v86)
hg.pairs <- readRDS(system.file("exdata", "human_cycle_markers.rds", package="scran"))
all_genes <- ensembldb::genes(EnsDb.Hsapiens.v86)
detach(package:EnsDb.Hsapiens.v86)
detach(package:ensembldb)

# gene_name_2_id <- function(gene){
#    return(all_genes[all_genes$gene_name==gene,]$gene_id[1])
# }
# 
# gene_ids <- sapply(mofa_trained@features_metadata$feature, gene_name_2_id)
# rowData(sce)["gene_id"] <- gene_ids
# rowData(sce)["gene_name"] <- rownames(sce)

gene_names_reactome <- all_genes[colnames(reactomeGS)]$gene_name
colnames(reactomeGS) <- gene_names_reactome

Subset to genes tested

reactomeGS_universe <- reactomeGS[, colnames(reactomeGS) %in% mofa_trained@features_metadata$feature]
# GSEA on positive weights, with default options
res.positive <- run_enrichment(mofa_trained,
  view='corrected_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "positive",
)

# GSEA on negative weights, with default options
res.negative <- run_enrichment(mofa_trained, 
  view='scaled_logcounts',
  # statistical.test = 'cor.adj.parametric',
  feature.sets = reactomeGS_universe, 
  sign = "negative"
)


for (f in 1:mofa_trained@dimensions$K){
  if (min(res.positive$pval.adj[,paste0("Factor", f)]) < 0.1) {
    print(plot_enrichment(res.positive, factor = f, alpha=0.1) + ggtitle("Positive weights") +
            plot_enrichment(res.negative, factor = f, alpha=0.1) + ggtitle("Negative weights") +
              plot_annotation(title=paste0("Factor", f)))
      }
  }
signif_pathways <- rownames(data.frame(res.negative$pval.adj))[order(data.frame(res.negative$pval.adj)[["Factor8"]])[0:10]]
colnames(reactomeGS_universe)[reactomeGS_universe[signif_pathways[5],]==1]
plot_enrichment_detailed(res.negative, factor = 8)

Notes

  • Factor2 separates BM from rest
  • Factor5: immature VS mature B cell phenotype, separates mature B cells and B1 cells in liver and BM from the others, more mature phenotype (lower expr of VPREB1 and co.)
#### KNN graph per celltype
```r ## Get factors that explain most variance in each celltype get_top_factor_per_celltype <- function(mofa_trained, gr, min_R2=2){ get_variance_explained(mofa_trained, as.data.frame = TRUE)[[1]] %>% dplyr::filter(group==gr) %>% dplyr::filter(value >= min_R2) %>% dplyr::pull(factor) %>% as.character() }
## Make KNN graph based on similarity of top factors for each celltype get_ct_KNN_graph <- function(mofa_trained, gr, min_R2=5, k=5){ ## Get factors that explain most variance per celltype fs <- get_top_factor_per_celltype(mofa_trained, gr, min_R2 = min_R2)
## Exclude technical factors fs <- fs[!fs %in% exclude_factors]
## Make KNN graph from top factors Z <- get_factors(mofa_trained, groups=gr, factors = fs)[[1]] knn_ct <- buildKNNGraph(t(Z), k=k)
## Add attributes metadata_ct <- samples_metadata(mofa_trained)[rownames(Z),] # covariates V(knn_ct)\(organ <- metadata_ct\)organ V(knn_ct)\(age <- metadata_ct\)age V(knn_ct)\(n_cells <- metadata_ct\)n_cells V(knn_ct)\(method <- metadata_ct\)method V(knn_ct)\(donor <- metadata_ct\)donor # top factors for (c in colnames(Z)){ vertex_attr(knn_ct)[[c]] <- Z[,c] }
return(knn_ct) }
## Plot KNN graph plot_ct_KNN_graph <- function(knn, color_by=“organ”){ ## Define color if (!color_by %in% names(vertex_attr(knn))){ stop(“specified color_by variable is not in vertex_attr(knn)”) }
if (color_by==“organ”){ scale_color_knngraph <- scale_color_manual(values=org_colors) } else if (is.numeric(vertex_attr(knn, color_by))){ scale_color_knngraph <- scale_color_viridis_c(option=“magma”) } else { scale_color_knngraph <- scale_color_discrete() }
vertex_attr(knn, “color_by”) <- vertex_attr(knn, color_by)
ggraph(knn) + geom_edge_link0() + geom_node_point(aes(color=color_by, size=n_cells)) + theme(panel.background = element_blank()) + scale_color_knngraph + scale_size(range=c(2,7)) }
knn_graph_pl <- lapply(all_groups, function(g){ knn <- get_ct_KNN_graph(mofa_trained, g, k=5, min_R2 = 2) plot_ct_KNN_graph(knn, color_by = ‘organ’) + ggtitle(g) })
knn_graph_pl ```
```r ## Score connectivity between samples from the same organ .calc_connectivity_score <- function(knn, o){ adj <- get.adjacency(knn) n_org <- sum(V(knn)\(organ==o) n_other <- sum(V(knn)\)organ!=o) within_edges <- sum(adj[V(knn)\(organ==o,V(knn)\)organ==o]) between_edges <- sum(adj[V(knn)\(organ==o,V(knn)\)organ!=o]) score <- (within_edges/between_edges)*(n_other/n_org) return(score) }
## Calculate connectivity score for permutations of node labels conn_score_test <- function(knn, o, n_perm=1000){ real_score <- .calc_connectivity_score(knn, o) ## Random permutations rand_scores <- c() for (i in 1:n_perm){ rand_knn <- knn V(rand_knn)\(organ <- sample(V(knn)\)organ) rand_scores <- c(rand_scores, .calc_connectivity_score(rand_knn, o)) }
p_val <- sum(c(rand_scores, real_score) >= real_score)/(n_perm + 1) if (p_val < 2e-16){ p_val <- 2e-16} return(c(‘score’=real_score,‘p_value’=p_val)) }
## Calculate connectivity score + significance with permutation test test_conn_group <- function(mofa_trained, g, k=5, min_R2 = 2, n_perm=1000){ knn <- get_ct_KNN_graph(mofa_trained, g, k=k, min_R2 = min_R2) test_orgs <- names(table(V(knn)\(organ))[table(V(knn)\)organ) > 2] return(sapply(test_orgs, function(o) conn_score_test(knn, o, n_perm=n_perm))) }
## Test in widespread cell types connectivity_test_ls <- lapply(anno_order[1:10], function(g) test_conn_group(mofa_trained, g)) connectivity_test_ls <- setNames(connectivity_test_ls, anno_order[1:10])
connectivity_test_df <- imap(connectivity_test_ls, ~ data.frame(t(.x)) %>% tibble::rownames_to_column(“organ”) %>% dplyr::mutate(group=.y)) %>% purrr::reduce(dplyr::bind_rows) %>% dplyr::mutate(is_signif = ifelse(p_value < 0.01, TRUE, FALSE))
connectivity_test_df %>% ggplot(aes(organ, group,fill=log10(score))) + geom_tile() + scale_fill_distiller(palette=“Reds”, direction = 1) + geom_text(data=. %>% dplyr::filter(is_signif), label="*", size=5)
```
r connectivity_test_df %>% dplyr::group_by(group) %>% dplyr::mutate(mean_val=median(score)) %>% dplyr::ungroup() %>% dplyr::arrange(-mean_val) %>% dplyr::mutate(group=factor(group, levels=unique(group))) %>% ggplot(aes(organ, log1p(score))) + geom_col(fill="grey") + geom_col(data=. %>% dplyr::filter(is_signif), aes(fill=organ)) + scale_fill_manual(values=org_colors) + coord_flip() + facet_grid(group~.) + theme(strip.text.y = element_text(angle=0))

–> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –> –>

–> –> –> –> –>

–> –> –>

LS0tCnRpdGxlOiAiRmFjdG9yIEFuYWx5c2lzIGZvciB3aXRoaW4tY2VsbHR5cGUgZGlmZmVyZW5jZXMgb24gb24gcGFuLWZldGFsIGltbXVuZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgRW52IHNldHVwCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKHJlbnYpCiMgcmVudjo6aW5pdCgpCiMgcmVudjo6aW5zdGFsbCgicmV0aWN1bGF0ZSIpCiMgcmVudjo6dXNlX3B5dGhvbigpCiMgCiMgcHlfcGtncyA8LSBjKAojICAgICAic2NhbnB5IiwKIyAgICAgImFubmRhdGEiLAojICAgICAibW9mYXB5MiIKIyApCiMgCiMgcmV0aWN1bGF0ZTo6cHlfaW5zdGFsbChweV9wa2dzKQojIAojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGMoIlNpbmdsZUNlbGxFeHBlcmltZW50IiwgInNjcmFuIiwgImJhdGNoZWxvciIsICJzY2F0ZXIiKSkKIyBpbnN0YWxsLnBhY2thZ2VzKGMoInBhdGNod29yayIpKQpgYGAKCmBgYHtyfQpyZW52OjphY3RpdmF0ZSgpCmBgYAoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShNT0ZBMikKICBsaWJyYXJ5KE1hdHJpeCkKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoc2NyYW4pCiAgbGlicmFyeShnbHVlKQogIGxpYnJhcnkoc2NhdGVyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoYmF0Y2hlbG9yKQogIGxpYnJhcnkocmhkZjUpCiAgbGlicmFyeShnZ3JhcGgpCiAgbGlicmFyeShpZ3JhcGgpCiAgfQogICkKYGBgCgpEZWZpbmUgcGxvdHRpbmcgdXRpbHMKYGBge3J9CnJlbW92ZV94X2F4aXMgPC0gZnVuY3Rpb24oKXsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpICAKfQoKcmVtb3ZlX3lfYXhpcyA8LSBmdW5jdGlvbigpewogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSkgIAp9CgpvcmdfY29sb3JzIDwtIHJlYWRfY3N2KCJ+L1Bhbl9mZXRhbF9pbW11bmUvbWV0YWRhdGEvb3JnYW5fY29sb3JzLmNzdiIpCm9yZ19jb2xvcnMgPC0gc2V0TmFtZXMob3JnX2NvbG9ycyRjb2xvciwgb3JnX2NvbG9ycyRvcmdhbikKYGBgCgpgYGB7cn0KZmlnZGlyIDwtICJ+L21vdW50L2dkcml2ZS9QYW5fZmV0YWwvVXBkYXRlc19hbmRfcHJlc2VudGF0aW9ucy9maWd1cmVzL01PRkFfYW5hbHlzaXNfTVlFTE9JRC8iCmlmICghZGlyLmV4aXN0cyhmaWdkaXIpKXsgZGlyLmNyZWF0ZShmaWdkaXIpIH0KYGBgCgojIyBMb2FkIHBzZXVkb2J1bGtlZCBkYXRhCgpgYGB7cn0Kc3BsaXQgPSAiTVlFTE9JRCIKaW5kaXIgPC0gZ2x1ZSgiL25mcy90ZWFtMjA1L2VkNi9kYXRhL0ZldGFsX2ltbXVuZS9MTU1fZGF0YS9MTU1faW5wdXRfe3NwbGl0fV9QQlVMSy8iKQoKbWF0cml4IDwtIHJlYWRNTShmaWxlID0gcGFzdGUwKGluZGlyLCAibWF0cml4Lm10eC5neiIpKQpjb2xkYXRhIDwtIHJlYWQuY3N2KGZpbGUgPSBwYXN0ZTAoaW5kaXIsICJtZXRhZGF0YS5jc3YuZ3oiKSkgICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiWCIpCnJvd2RhdGEgPC0gcmVhZC5jc3YoZmlsZSA9IHBhc3RlMChpbmRpciwgImdlbmUuY3N2Lmd6IikpIAoKIyMgTWFrZSBTaW5nbGVDZWxsRXhwZXJpbWVudCBvYmoKc2NlIDwtIFNpbmdsZUNlbGxFeHBlcmltZW50KGxpc3QobG9nY291bnRzID0gdChtYXRyaXgpKSwgY29sRGF0YSA9IGNvbGRhdGEpCnJvd25hbWVzKHNjZSkgPC0gbWFrZS51bmlxdWUocm93ZGF0YSRHZW5lTmFtZSkgCmBgYAoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD0xMH0KIyMgUGxvdCBudW1iZXIgb2YgY2VsbHMgcGVyIG9yZ2FuL2NlbGx0eXBlIHBhaXIKbl9jZWxsc19oZWF0bWFwIDwtIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikgJT4lCiAgc3VtbWFyaXNlKG5fY2VsbHM9c3VtKG5fY2VsbHMpKSAlPiUKICBnZ3Bsb3QoYWVzKGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSkgKwogIGdlb21fdGlsZShhZXMoZmlsbD1sb2cxMChuX2NlbGxzKSkpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPW5fY2VsbHMpLCBjb2xvcj0id2hpdGUiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNikgKwogIHhsYWIoImNlbGx0eXBlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkKCm5fc2FtcGxlc19oZWF0bWFwIDwtIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUKICBncm91cF9ieShhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikgJT4lCiAgc3VtbWFyaXNlKG5fc2FtcGxlcz1uKCkpICU+JQogIGdncGxvdChhZXMoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPW5fc2FtcGxlcykpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPW5fc2FtcGxlcyksIGNvbG9yPSJ3aGl0ZSIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb249ImNpdmlkaXMiKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemUgPSAxNikgKwogIHhsYWIoImNlbGx0eXBlIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwLCBoanVzdD0xLCB2anVzdD0wLjUpKQoKbl9jZWxsc19oZWF0bWFwIC8gbl9zYW1wbGVzX2hlYXRtYXAKYGBgCgojIyBQcmVwcm9jZXNzaW5nCgojIyMgRmlsdGVyaW5nIHNhbXBsZXMKCmBgYHtyfQojIyBGaWx0ZXIgb3V0IHNhbXBsZXMgd2l0aCBsZXNzIHRoYW4gMjAgY2VsbHMKc2NlIDwtIHNjZVssc2NlJG5fY2VsbHMgPiAyMF0KCiMgRXhjbHVkZSBjZWxsdHlwZXMgcHJlc2VudCBpbiBqdXN0IG9uZSBvcmdhbgprZWVwX2N0IDwtIGRhdGEuZnJhbWUoY29sRGF0YShzY2UpKSAlPiUKICBkcGx5cjo6c2VsZWN0KG9yZ2FuLCBhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUKICBkaXN0aW5jdCgpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JQogIHN1bW1hcmlzZShuPW4oKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGZpbHRlcihuID4gMSkgJT4lCiAgcHVsbChhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKQoKc2NlIDwtIHNjZVssc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4gJWluJSBrZWVwX2N0XQoKIyBGaWx0ZXIgb3V0IGNlbGx0eXBlcyB3aXRoIGxlc3MgdGhhbiAxMCBzYW1wbGVzCmtlZXBfY3QgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JQogIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUKICBmaWx0ZXIobl9zYW1wbGVzID49IDEwKSAlPiUKICBwdWxsKGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCgpzY2UgPC0gc2NlWyxzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbiAlaW4lIGtlZXBfY3RdCgojIyBFeGNsdWRlIGxvdyBxdWFsaXR5IGNsdXN0ZXJzCmFubm9fZ3JvdXBzIDwtIGpzb25saXRlOjpmcm9tSlNPTih0eHQgPSAgIn4vUGFuX2ZldGFsX2ltbXVuZS9tZXRhZGF0YS9hbm5vX2dyb3Vwcy5qc29uIikKc2NlIDwtIHNjZVssIXNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuICVpbiUgYW5ub19ncm91cHMkT1RIRVJdCgojIyBFeGNsdWRlIGRvbm9yIEYxOSAobG93IFEpCnNjZSA8LSBzY2VbLCFzY2UkZG9ub3IgJWluJSBjKCdGMTknKV0KYGBgCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQojIyBQbG90IG51bWJlciBvZiBjZWxscyBwZXIgb3JnYW4vY2VsbHR5cGUgcGFpcgpuX2NlbGxzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9jZWxscz1zdW0obl9jZWxscykpICU+JQogIGdncGxvdChhZXMoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgb3JnYW4pKSArCiAgZ2VvbV90aWxlKGFlcyhmaWxsPWxvZzEwKG5fY2VsbHMpKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9jZWxscyksIGNvbG9yPSJ3aGl0ZSIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpKQoKbl9zYW1wbGVzX2hlYXRtYXAgPC0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JQogIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3RpbGUoYWVzKGZpbGw9bl9zYW1wbGVzKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bl9zYW1wbGVzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbj0iY2l2aWRpcyIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KSArCiAgeGxhYigiY2VsbHR5cGUiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTEsIHZqdXN0PTAuNSkpCgpuX2NlbGxzX2hlYXRtYXAgLyBuX3NhbXBsZXNfaGVhdG1hcApgYGAKCmBgYHtyLCBmaWcud2lkdGg9MTUsIGZpZy5oZWlnaHQ9MTB9Cm9yZ2FuX29yZGVyIDwtIGMoIllTIiwgIkxJIiwgIkJNIiwgIlRIIiwgIlNQIiwgIlNLIiwiS0kiLCJHVSIsICJNTE4iKQpwbCA8LSBkYXRhLmZyYW1lKGNvbERhdGEoc2NlKSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUKICBkcGx5cjo6c3VtbWFyaXNlKG5fY2VsbHM9c3VtKG5fY2VsbHMpLCBuX3NhbXBsZXM9ZHBseXI6Om4oKSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JQogIGRwbHlyOjptdXRhdGUobl9vcmdhbnM9ZHBseXI6Om4oKSwgb3JnX2ZyYWM9bl9jZWxscy9zdW0obl9jZWxscykpICU+JQogICMjIGlzIHRoZSBjdCBvdmVycmVwcmVzZW50ZWQgaW4gb25lIG9yZ2FuPwogIGRwbHlyOjptdXRhdGUoZGVsdGFfbWF4X29yZ19mcmFjID0gbWF4KG9yZ19mcmFjKS1vcmdfZnJhYykgJT4lIAogIGRwbHlyOjptdXRhdGUobWVhbl9kZWx0YV9tYXhfb3JnX2ZyYWMgPSBtZWFuKGRlbHRhX21heF9vcmdfZnJhYykpICU+JSAKICBkcGx5cjo6dW5ncm91cCgpICU+JQogIGRwbHlyOjphcnJhbmdlKG5fb3JnYW5zLCAtbWVhbl9kZWx0YV9tYXhfb3JnX2ZyYWMpICU+JQogIGRwbHlyOjptdXRhdGUoYW5ub19sdmxfMl9maW5hbF9jbGVhbj1mYWN0b3IoYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgbGV2ZWxzPXJldih1bmlxdWUoYW5ub19sdmxfMl9maW5hbF9jbGVhbikpKSkgJT4lCiAgZHBseXI6Om11dGF0ZShvcmdhbj1mYWN0b3Iob3JnYW4sIGxldmVscz1yZXYob3JnYW5fb3JkZXIpKSkgJT4lCiAgZ2dwbG90KGFlcyhhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuLCBvcmdhbikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvcj1sb2cxMChuX2NlbGxzKSwgc2l6ZT1uX3NhbXBsZXMpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uX2NlbGxzKSwgY29sb3I9IndoaXRlIikgKwogIHNjYWxlX3NpemUocmFuZ2U9Yyg3LDE4KSwgYnJlYWtzID0gYygwLDEsMTAsMzApLCBuYW1lPSIjIHNhbXBsZXMiKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19jKG5hbWU9ImxvZzEwKCMgY2VsbHMpIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMjApICsKICB4bGFiKCJjZWxsdHlwZSIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCwgaGp1c3Q9MSwgdmp1c3Q9MC41KSkgCgojIyBTYXZlIG9yZGVyIG9mIENUcyBmcm9tIHdpZGVzcHJlYWQgdG8gcmVzdHJpY3RlZAphbm5vX29yZGVyIDwtIGxldmVscyhwbCRkYXRhJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCgpnZ3NhdmUocGFzdGUwKGZpZ2RpciwgImN0X29yZ2FuX2Rpc3RyaWJ1dGlvbi5wZGYiKSwgcGwsIHdpZHRoID0gMTUsIGhlaWdodCA9IDEwKQpgYGAKCgoKIyMjIFRlY2huaWNhbCBlZmZlY3QgY29ycmVjdGlvbiAKCmBgYHtyfQojIyBGZWF0dXJlIHNlbGVjdGlvbiB3IHNjcmFuIFdJVEhJTiBDRUxMVFlQRQphbm5vX2dyb3VwcyA8LSBzcGxpdChjb2xuYW1lcyhzY2UpLCBzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbikKYWxsX2h2Z3MgPC0gYygpCmZvciAoaSBpbiBhbm5vX2dyb3Vwcyl7CiAgZGVjIDwtIG1vZGVsR2VuZVZhcihzY2VbLGldKQogIGh2Z3MgPC0gZ2V0VG9wSFZHcyhkZWMsIG4gPSAxMDAwKQogIGFsbF9odmdzIDwtIHVuaW9uKGFsbF9odmdzLCBodmdzKQogIH0KCnNjZSA8LSBzY2Vbd2hpY2gocm93U3Vtcyhsb2djb3VudHMoc2NlKSkgPiAwKSxdCnNjZQpgYGAKCkVEQSB3aXRoIFBDQQpgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1fQpzY2UgPC0gcnVuUENBKHNjZSwgc2NhbGU9VFJVRSwgbmNvbXBvbmVudHM9MzAsIAogICAgICAgICAgICAgIGV4cHJzX3ZhbHVlcyA9ICJsb2djb3VudHMiLCBzdWJzZXRfcm93PWFsbF9odmdzKQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJkb25vciIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im1ldGhvZCIsIG5jb21wb25lbnRzPTYpCnBsb3RQQ0Eoc2NlLCBjb2xvdXJfYnk9Im9yZ2FuIiwgbmNvbXBvbmVudHM9MTApCmBgYAoKTWluaW1pemUgb2J2aW91cyB0ZWNobmljYWwgZWZmZWN0cyAoM0dFWC81R0VYLCBkb25vcikgdXNpbmcgbGluZWFyIHJlZ3Jlc3Npb24gKGZvbGxvd2luZyBwcm9jZWR1cmUgZnJvbSBbT1NDQV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS9pbnRlZ3JhdGluZy1kYXRhc2V0cy5odG1sI2xpbmVhci1yZWdyZXNzaW9uKSkKCmBgYHtyfQojIyBSZWdyZXNzIHRlY2huaWNhbCBlZmZlY3RzCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmRvbm9yK21ldGhvZCxkYXRhPWNvbERhdGEoc2NlKSkKcmVzaWR1YWxzIDwtIHJlZ3Jlc3NCYXRjaGVzKHNjZSwgYXNzYXkudHlwZSA9ICJsb2djb3VudHMiLCBkZXNpZ24gPSBkZXNpZ24pCmFzc2F5KHNjZSwgImNvcnJlY3RlZF9sb2djb3VudHMiKSA8LSBhcy5tYXRyaXgoYXNzYXkocmVzaWR1YWxzWyxjb2xuYW1lcyhzY2UpXSwgImNvcnJlY3RlZCIpKQoKIyMgUmVncmVzcyBvcmdhbiAoc291cCBlZmZlY3QpCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofm9yZ2FuLGRhdGE9Y29sRGF0YShzY2UpKSAjIyBJbmNsdWRlIG9yZ2FuIHRlcm0gdG8gY2FwdHVyZSBzb3VwCnJlc2lkdWFscyA8LSByZWdyZXNzQmF0Y2hlcyhzY2UsIGFzc2F5LnR5cGUgPSAiY29ycmVjdGVkX2xvZ2NvdW50cyIsIGRlc2lnbiA9IGRlc2lnbikKYXNzYXkoc2NlLCAiY29ycmVjdGVkX2xvZ2NvdW50cyIpIDwtIGFzLm1hdHJpeChhc3NheShyZXNpZHVhbHNbLGNvbG5hbWVzKHNjZSldLCAiY29ycmVjdGVkIikpCgpgYGAKCkNoZWNrIHJlZ3Jlc3Npb24gaGFzIGFuIGVmZmVjdCByZXBlYXRpbmcgUENBCmBgYHtyLCBmaWcuaGVpZ2h0PTE1LCBmaWcud2lkdGg9MTV9CnNjZSA8LSBydW5QQ0Eoc2NlLCBzY2FsZT1UUlVFLCBuY29tcG9uZW50cz0zMCwgZXhwcnNfdmFsdWVzID0gImNvcnJlY3RlZF9sb2djb3VudHMiKQoKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0ibWV0aG9kIiwgbmNvbXBvbmVudHM9NikKcGxvdFBDQShzY2UsIGNvbG91cl9ieT0iZG9ub3IiLCBuY29tcG9uZW50cz02KQpwbG90UENBKHNjZSwgY29sb3VyX2J5PSJvcmdhbiIsIG5jb21wb25lbnRzPTgpCmBgYAoKIyMjIEZlYXR1cmUgc2VsZWN0aW9uCgpgYGB7cn0KIyMgRmVhdHVyZSBzZWxlY3Rpb24gdyBzY3JhbiBXSVRISU4gQ0VMTFRZUEUKYW5ub19ncm91cHMgPC0gc3BsaXQoY29sbmFtZXMoc2NlKSwgc2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pCmFsbF9odmdzIDwtIGMoKQpmb3IgKGkgaW4gYW5ub19ncm91cHMpewogIGRlYyA8LSBtb2RlbEdlbmVWYXIoc2NlWyxpXSwgYXNzYXkudHlwZSA9ICJjb3JyZWN0ZWRfbG9nY291bnRzIikKICBodmdzIDwtIGdldFRvcEhWR3MoZGVjLCBuID0gMTAwMCkKICBhbGxfaHZncyA8LSB1bmlvbihhbGxfaHZncywgaHZncykKICB9CmBgYAoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZGF0YS5mcmFtZShjb2xEYXRhKHNjZSkpICU+JSAtLT4KPCEtLSAgIGdyb3VwX2J5KGFubm9fbHZsXzJfZmluYWxfY2xlYW4sIG9yZ2FuKSAlPiUgLS0+CjwhLS0gICBzdW1tYXJpc2Uobl9zYW1wbGVzPW4oKSwgbl9jZWxscz1zdW0obl9jZWxscykpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMobl9zYW1wbGVzLCBsb2cxMChuX2NlbGxzKSkpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KHNpemU9MC44LCBhbHBoYT0wLjYpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0gLS0+CjwhLS0gcCA8LSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlKVssMToyXSkgJT4lIC0tPgo8IS0tICAgbXV0YXRlKG9yZ2FuPXNjZSRvcmdhbiwgY2VsbHR5cGU9c2NlJGFubm9fbHZsXzJfZmluYWxfY2xlYW4pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShjb2xvcj1pZmVsc2UoY2VsbHR5cGU9PSJNQVRVUkUgQiBDRUxMIiwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzEsIFBDMikpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpIC0tPgoKPCEtLSBwIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkoUkNvbG9yQnJld2VyKSAtLT4KPCEtLSBvcmdfY29sb3JzIDwtIHNldE5hbWVzKGJyZXdlci5wYWwoOSwgIlNldDEiKSwgdW5pcXVlKHNjZSRvcmdhbikpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzY2VfbWF0dXJlQiA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuPT0iTUFUVVJFIEIgQ0VMTCJdIC0tPgo8IS0tIGFzc2F5KHNjZV9tYXR1cmVCLCAic2NhbGVkX2xvZ2NvdW50cyIpIDwtIHQoc2NhbGUodChsb2djb3VudHMoc2NlX21hdHVyZUIpKSkpIC0tPgoKCjwhLS0gc2NlX21hdHVyZUIgPC0gcnVuUENBKHNjZV9tYXR1cmVCLCBzY2FsZT1GQUxTRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJzY2FsZWRfbG9nY291bnRzIikgLS0+Cgo8IS0tIGRhdGEuZnJhbWUocmVkdWNlZERpbShzY2VfbWF0dXJlQilbLDI6M10pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2VfbWF0dXJlQiRvcmdhbiwgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShvcmdhbiAlaW4lIGMoIlRIIiwiQk0iKSwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzIsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemU9MTYpICsgLS0+CjwhLS0gICBnZ3RpdGxlKCJNQVRVUkUgQiBDRUxMIikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBzY2VfbWF0dXJlQiA8LSBzY2VbLHNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuPT0iTksiXSAtLT4KPCEtLSBhc3NheShzY2VfbWF0dXJlQiwgInNjYWxlZF9sb2djb3VudHMiKSA8LSB0KHNjYWxlKHQobG9nY291bnRzKHNjZV9tYXR1cmVCKSkpKSAtLT4KCjwhLS0gc2NlX21hdHVyZUIgPC0gcnVuUENBKHNjZV9tYXR1cmVCLCBzY2FsZT1GQUxTRSwgbmNvbXBvbmVudHM9MzAsIGV4cHJzX3ZhbHVlcyA9ICJzY2FsZWRfbG9nY291bnRzIikgLS0+Cgo8IS0tICMjIFZhcmlhbmNlIGV4cGxhaW5lZCAtLT4KPCEtLSBkYXRhLmZyYW1lKHJlZHVjZWREaW0oc2NlX21hdHVyZUIpWywxOjRdKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUob3JnYW49c2NlX21hdHVyZUIkb3JnYW4sICAtLT4KPCEtLSAgICAgICAgICBjZWxsdHlwZT1zY2VfbWF0dXJlQiRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY29sb3I9aWZlbHNlKG9yZ2FuICVpbiUgYygiR1UiLCAiU1AiKSwgIkVMUCIsIE5BKSkgJT4lIC0tPgo8IS0tICAgZ2dwbG90KGFlcyhQQzEsIFBDMykpICsgLS0+CjwhLS0gICBnZW9tX3BvaW50KGNvbG9yPSJncmV5IikgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIHNpemU9MikgKyAtLT4KPCEtLSAgIGdlb21fcnVnKGRhdGE9LiAlPiUgZmlsdGVyKCFpcy5uYShjb2xvcikpLCBhZXMoY29sb3I9b3JnYW4pLCBhbHBoYT0wLjUpICsgLS0+CjwhLS0gICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICB0aGVtZV9idyhiYXNlX3NpemU9MTYpICsgLS0+CjwhLS0gICBnZ3RpdGxlKCJOSyBDRUxMIikgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGRhdGEuZnJhbWUocmVkdWNlZERpbShzY2VfbWF0dXJlQilbLDI6M10pICU+JSAtLT4KPCEtLSAgIG11dGF0ZShvcmdhbj1zY2VfbWF0dXJlQiRvcmdhbiwgY2VsbHR5cGU9c2NlX21hdHVyZUIkYW5ub19sdmxfMl9maW5hbF9jbGVhbikgJT4lIC0tPgo8IS0tICAgbXV0YXRlKGNvbG9yPWlmZWxzZShjZWxsdHlwZT09Ik1BVFVSRSBCIENFTEwiLCAiRUxQIiwgTkEpKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKFBDMiwgUEMzKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoY29sb3I9ImdyZXkiKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChkYXRhPS4gJT4lIGZpbHRlcighaXMubmEoY29sb3IpKSwgYWVzKGNvbG9yPW9yZ2FuKSwgc2l6ZT0yKSArIC0tPgo8IS0tICAgZ2VvbV9ydWcoZGF0YT0uICU+JSBmaWx0ZXIoIWlzLm5hKGNvbG9yKSksIGFlcyhjb2xvcj1vcmdhbiksIGFscGhhPTAuNSkgKyAtLT4KPCEtLSAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTcGVjdHJhbCIpIC0tPgo8IS0tIGBgYCAtLT4KCgojIEZBIE1vZGVsIC0gTm9ybWFsIE1PRkEgLyBvbmx5IGNlbGx0eXBlcyBhcyBncm91cHMKCk1ha2UgTU9GQSBvYmplY3QgKFVzZSBjZWxsdHlwZXMgYXMgZ3JvdXBpbmcgY292YXJpYXRlKQoKYGBge3J9CnNjZSRhbm5vX2x2bF8yX2ZpbmFsX2NsZWFuIDwtIHN0cl9yZXBsYWNlX2FsbChzY2UkYW5ub19sdmxfMl9maW5hbF9jbGVhbiwgIi8iLCJfIikKbW9mYSA8LSBjcmVhdGVfbW9mYV9mcm9tX1NpbmdsZUNlbGxFeHBlcmltZW50KHNjZVthbGxfaHZncyxdLCBhc3NheSA9ICJjb3JyZWN0ZWRfbG9nY291bnRzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cHMgPSAiYW5ub19sdmxfMl9maW5hbF9jbGVhbiIsIGV4dHJhY3RfbWV0YWRhdGEgPSBUUlVFKQoKc2F2ZVJEUyhtb2ZhLCBnbHVlKCd7aW5kaXJ9TVlFTE9JRF9tb2ZhX29ial9vcmdhbkNvcnJlY3RlZC5SRFMnKSkKbW9mYV9vYmogPC0gcmVhZFJEUyhnbHVlKCd7aW5kaXJ9TVlFTE9JRF9tb2ZhX29ial9vcmdhbkNvcnJlY3RlZC5SRFMnKSkKYGBgCgpgYGB7cn0Kb2JqZWN0IDwtIG1vZmFfb2JqCmBgYAoKClByZXBhcmUgNCB0cmFpbmluZwoKYGBge3J9CgpkYXRhX29wdHMgPC0gZ2V0X2RlZmF1bHRfZGF0YV9vcHRpb25zKG9iamVjdCkKZGF0YV9vcHRzJHVzZV9mbG9hdDMyIDwtIFRSVUUKZGF0YV9vcHRzJGNlbnRlcl9ncm91cHMgPC0gRkFMU0UKb2JqZWN0QGRhdGFfb3B0aW9ucyA8LSBkYXRhX29wdHMKCm1vZGVsX29wdHMgPC0gZ2V0X2RlZmF1bHRfbW9kZWxfb3B0aW9ucyhvYmplY3QpCm1vZGVsX29wdHMkbnVtX2ZhY3RvcnMgPC0gMzAKIyBtb2RlbF9vcHRzJGFyZF9mYWN0b3JzIDwtIEZBTFNFCgp0cmFpbl9vcHRzIDwtIGdldF9kZWZhdWx0X3RyYWluaW5nX29wdGlvbnMob2JqZWN0KQp0cmFpbl9vcHRzJHNlZWQgPC0gMjAyMAp0cmFpbl9vcHRzJGNvbnZlcmdlbmNlX21vZGUgPC0gIm1lZGl1bSIgIyB1c2UgImZhc3QiIGZvciBmYXN0ZXIgdHJhaW5pbmcKdHJhaW5fb3B0cyRzdG9jaGFzdGljIDwtIEZBTFNFCgojIG1lZmlzdG9fb3B0cyA8LSBnZXRfZGVmYXVsdF9tZWZpc3RvX29wdGlvbnMob2JqZWN0KQojIG1lZmlzdG9fb3B0cyR3YXJwaW5nIDwtIEZBTFNFCiMgbWVmaXN0b19vcHRzJHNwYXJzZUdQIDwtIFRSVUUKCm9iamVjdCA8LSBwcmVwYXJlX21vZmEoCiAgb2JqZWN0ID0gb2JqZWN0LAogIGRhdGFfb3B0aW9ucyA9IGRhdGFfb3B0cywKICBtb2RlbF9vcHRpb25zID0gbW9kZWxfb3B0cywKICB0cmFpbmluZ19vcHRpb25zID0gdHJhaW5fb3B0cwopIAoKb2JqZWN0CmBgYAoKIyMgVHJhaW4KCldyYXBwZWQgaW4gYHJ1bl9tb2ZhLlJgCgoKYGBge3J9Cm91dGZpbGUgPC0gZ2x1ZSgne2luZGlyfXtzcGxpdH1fbW9mYV9tb2RlbF9vbmV2aWV3X29yZ2FuQ29ycmVjdGVkLmhkZjUnKQptb2ZhX3RyYWluZWQgPC0gcnVuX21vZmEob2JqZWN0LCBvdXRmaWxlID0gb3V0ZmlsZSkKYGBgCgpgYGB7cn0KIyMjIFR3ZWFraW5nIHRoZSBNT0ZBMiBsb2FkaW5nIGZ1bmN0aW9uIGJlY2F1c2UgdGhlIHF1YWxpdHkgY29udHJvbCBjb21wbGFpbnMKbG9hZF9tb2RlbCA8LSBmdW5jdGlvbihmaWxlLCBzb3J0X2ZhY3RvcnMgPSBUUlVFLCBvbl9kaXNrID0gRkFMU0UsIGxvYWRfZGF0YSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX291dGxpZXJzID0gRkFMU0UsIHJlbW92ZV9pbmFjdGl2ZV9mYWN0b3JzID0gVFJVRSwgdmVyYm9zZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIGxvYWRfaW50ZXJwb2xfWiA9IEZBTFNFKSB7CgogICMgQ3JlYXRlIG5ldyBNT0ZBb2RlbCBvYmplY3QKICBvYmplY3QgPC0gbmV3KCJNT0ZBIikKICBvYmplY3RAc3RhdHVzIDwtICJ0cmFpbmVkIgogIAogICMgU2V0IG9uX2Rpc2sgb3B0aW9uCiAgaWYgKG9uX2Rpc2spIHsgCiAgICBvYmplY3RAb25fZGlzayA8LSBUUlVFIAogIH0gZWxzZSB7IAogICAgICBvYmplY3RAb25fZGlzayA8LSBGQUxTRSAKICB9CiAgCiAgIyBHZXQgZ3JvdXBzIGFuZCBkYXRhIHNldCBuYW1lcyBmcm9tIHRoZSBoZGY1IGZpbGUgb2JqZWN0CiAgaDVscy5vdXQgPC0gaDVscyhmaWxlLCBkYXRhc2V0aW5mbyA9IEZBTFNFKQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgdHJhaW5pbmcgZGF0YSAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKICAjIExvYWQgbmFtZXMKICBpZiAoInZpZXdzIiAlaW4lIGg1bHMub3V0JG5hbWUpIHsKICAgIHZpZXdfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKCBoNXJlYWQoZmlsZSwgInZpZXdzIilbWzFdXSApCiAgICBncm91cF9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiZ3JvdXBzIilbWzFdXSApCiAgICBmZWF0dXJlX25hbWVzIDwtIGg1cmVhZChmaWxlLCAiZmVhdHVyZXMiKVt2aWV3X25hbWVzXQogICAgc2FtcGxlX25hbWVzICA8LSBoNXJlYWQoZmlsZSwgInNhbXBsZXMiKVtncm91cF9uYW1lc10gCiAgfSBlbHNlIHsgICMgZm9yIG9sZCBtb2RlbHMKICAgIGZlYXR1cmVfbmFtZXMgPC0gaDVyZWFkKGZpbGUsICJmZWF0dXJlcyIpCiAgICBzYW1wbGVfbmFtZXMgIDwtIGg1cmVhZChmaWxlLCAic2FtcGxlcyIpCiAgICB2aWV3X25hbWVzIDwtIG5hbWVzKGZlYXR1cmVfbmFtZXMpCiAgICBncm91cF9uYW1lcyA8LSBuYW1lcyhzYW1wbGVfbmFtZXMpCiAgICBoNWxzLm91dCA8LSBoNWxzLm91dFtncmVwKCJ2YXJpYW5jZV9leHBsYWluZWQiLCBoNWxzLm91dCRuYW1lLCBpbnZlcnQgPSBUUlVFKSxdCiAgfQogIGlmKCJjb3ZhcmlhdGVzIiAlaW4lICBoNWxzLm91dCRuYW1lKXsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBhcy5jaGFyYWN0ZXIoIGg1cmVhZChmaWxlLCAiY292YXJpYXRlcyIpW1sxXV0pCiAgfSBlbHNlIHsKICAgIGNvdmFyaWF0ZV9uYW1lcyA8LSBOVUxMCiAgfQoKICAjIExvYWQgdHJhaW5pbmcgZGF0YSAoYXMgbmVzdGVkIGxpc3Qgb2YgbWF0cmljZXMpCiAgZGF0YSA8LSBsaXN0KCk7IGludGVyY2VwdHMgPC0gbGlzdCgpCiAgaWYgKGxvYWRfZGF0YSAmJiAiZGF0YSIlaW4laDVscy5vdXQkbmFtZSkgewogICAgCiAgICBvYmplY3RAZGF0YV9vcHRpb25zW1sibG9hZGVkIl1dIDwtIFRSVUUKICAgIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIGRhdGEuLi4iKQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGludGVyY2VwdHNbW21dXSA8LSBsaXN0KCkKICAgICAgZm9yIChnIGluIGdyb3VwX25hbWVzKSB7CiAgICAgICAgaWYgKG9uX2Rpc2spIHsKICAgICAgICAgICMgYXMgRGVsYXllZEFycmF5cwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApICkKICAgICAgICB9IGVsc2UgewogICAgICAgICAgIyBhcyBtYXRyaWNlcwogICAgICAgICAgZGF0YVtbbV1dW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImRhdGEvJXMvJXMiLCBtLCBnKSApCiAgICAgICAgICB0cnlDYXRjaChpbnRlcmNlcHRzW1ttXV1bW2ddXSA8LSBhcy5udW1lcmljKCBoNXJlYWQoZmlsZSwgc3ByaW50ZigiaW50ZXJjZXB0cy8lcy8lcyIsIG0sIGcpICkgKSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7IE5VTEwgfSkKICAgICAgICB9CiAgICAgICAgIyBSZXBsYWNlIE5hTiBieSBOQQogICAgICAgIGRhdGFbW21dXVtbZ11dW2lzLm5hbihkYXRhW1ttXV1bW2ddXSldIDwtIE5BICMgdGhpcyByZWFsaXNlZCBpbnRvIG1lbW9yeSwgVE8gRklYCiAgICAgIH0KICAgIH0KICAgIAogICMgQ3JlYXRlIGVtcHR5IHRyYWluaW5nIGRhdGEgKGFzIG5lc3RlZCBsaXN0IG9mIGVtcHR5IG1hdHJpY2VzLCB3aXRoIHRoZSBjb3JyZWN0IGRpbWVuc2lvbnMpCiAgfSBlbHNlIHsKICAgIAogICAgb2JqZWN0QGRhdGFfb3B0aW9uc1tbImxvYWRlZCJdXSA8LSBGQUxTRQogICAgCiAgICBmb3IgKG0gaW4gdmlld19uYW1lcykgewogICAgICBkYXRhW1ttXV0gPC0gbGlzdCgpCiAgICAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICAgICAgIGRhdGFbW21dXVtbZ11dIDwtIC5jcmVhdGVfbWF0cml4X3BsYWNlaG9sZGVyKHJvd25hbWVzID0gZmVhdHVyZV9uYW1lc1tbbV1dLCBjb2xuYW1lcyA9IHNhbXBsZV9uYW1lc1tbZ11dKQogICAgICB9CiAgICB9CiAgfQoKICBvYmplY3RAZGF0YSA8LSBkYXRhCiAgb2JqZWN0QGludGVyY2VwdHMgPC0gaW50ZXJjZXB0cwoKCiAgIyBMb2FkIG1ldGFkYXRhIGlmIGFueQogIGlmICgic2FtcGxlc19tZXRhZGF0YSIgJWluJSBoNWxzLm91dCRuYW1lKSB7CiAgICBvYmplY3RAc2FtcGxlc19tZXRhZGF0YSA8LSBiaW5kX3Jvd3MobGFwcGx5KGdyb3VwX25hbWVzLCBmdW5jdGlvbihnKSBhcy5kYXRhLmZyYW1lKGg1cmVhZChmaWxlLCBzcHJpbnRmKCJzYW1wbGVzX21ldGFkYXRhLyVzIiwgZykpKSkpCiAgfQogIGlmICgiZmVhdHVyZXNfbWV0YWRhdGEiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgb2JqZWN0QGZlYXR1cmVzX21ldGFkYXRhIDwtIGJpbmRfcm93cyhsYXBwbHkodmlld19uYW1lcywgZnVuY3Rpb24obSkgYXMuZGF0YS5mcmFtZShoNXJlYWQoZmlsZSwgc3ByaW50ZigiZmVhdHVyZXNfbWV0YWRhdGEvJXMiLCBtKSkpKSkKICB9CiAgCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIHNhbXBsZSBjb3ZhcmlhdGVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAKICAjIGlmIChhbnkoZ3JlcGwoImNvdl9zYW1wbGVzIiwgaDVscy5vdXQkZ3JvdXApKSl7CiAgIyAgIGNvdmFyaWF0ZXMgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiY292X3NhbXBsZXMvJXMiLCBnKSApICkKICAjICAgICB9IGVsc2UgewogICMgICAgICAgIyBhcyBtYXRyaWNlcwogICMgICAgICAgY292YXJpYXRlc1tbZ11dIDwtIGg1cmVhZChmaWxlLCBzcHJpbnRmKCJjb3Zfc2FtcGxlcy8lcyIsIGcpICkKICAjICAgICB9ICAgIAogICMgICB9CiAgIyB9IGVsc2UgY292YXJpYXRlcyA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlcyA8LSBjb3ZhcmlhdGVzCgogICMgaWYgKGFueShncmVwbCgiY292X3NhbXBsZXNfdHJhbnNmb3JtZWQiLCBoNWxzLm91dCRncm91cCkpKXsKICAjICAgY292YXJpYXRlc193YXJwZWQgPC0gbGlzdCgpCiAgIyAgIGZvciAoZyBpbiBncm91cF9uYW1lcykgewogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gRGVsYXllZEFycmF5OjpEZWxheWVkQXJyYXkoIEhERjVBcnJheVNlZWQoZmlsZSwgbmFtZSA9IHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKSApCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgICMgYXMgbWF0cmljZXMKICAjICAgICAgIGNvdmFyaWF0ZXNfd2FycGVkW1tnXV0gPC0gaDVyZWFkKGZpbGUsIHNwcmludGYoImNvdl9zYW1wbGVzX3RyYW5zZm9ybWVkLyVzIiwgZykgKQogICMgICAgIH0gICAgCiAgIyAgIH0KICAjIH0gZWxzZSBjb3ZhcmlhdGVzX3dhcnBlZCA8LSBOVUxMCiAgIyBvYmplY3RAY292YXJpYXRlc193YXJwZWQgPC0gY292YXJpYXRlc193YXJwZWQKICAKICAjICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyAjIyBMb2FkIGludGVycG9sYXRlZCBmYWN0b3IgdmFsdWVzICMjCiAgIyAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpbnRlcnBvbGF0ZWRfWiA8LSBsaXN0KCkKICAjIGlmIChpc1RSVUUobG9hZF9pbnRlcnBvbF9aKSkgewogICMgICAKICAjICAgaWYgKGlzVFJVRSh2ZXJib3NlKSkgbWVzc2FnZSgiTG9hZGluZyBpbnRlcnBvbGF0ZWQgZmFjdG9yIHZhbHVlcy4uLiIpCiAgIyAgIAogICMgICBmb3IgKGcgaW4gZ3JvdXBfbmFtZXMpIHsKICAjICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dIDwtIGxpc3QoKQogICMgICAgIGlmIChvbl9kaXNrKSB7CiAgIyAgICAgICAjIGFzIERlbGF5ZWRBcnJheXMKICAjICAgICAgICMgaW50ZXJwb2xhdGVkX1pbW2ddXSA8LSBEZWxheWVkQXJyYXk6OkRlbGF5ZWRBcnJheSggSERGNUFycmF5U2VlZChmaWxlLCBuYW1lID0gc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcyIsIGcpICkgKQogICMgICAgIH0gZWxzZSB7CiAgIyAgICAgICAjIGFzIG1hdHJpY2VzCiAgIyAgICAgICB0cnlDYXRjaCggewogICMgICAgICAgICBpbnRlcnBvbGF0ZWRfWltbZ11dW1sibWVhbiJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy9tZWFuIiwgZykgKQogICMgICAgICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJQcmVkaWNpdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJ2YXJpYW5jZSJdXSA8LSBoNXJlYWQoZmlsZSwgc3ByaW50ZigiWl9wcmVkaWN0aW9ucy8lcy92YXJpYW5jZSIsIGcpICkKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVmFyaWFuY2Ugb2YgcHJlZGljdGlvbnMgb2YgWiBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICAjICAgICAgIHRyeUNhdGNoKCB7CiAgIyAgICAgICAgIGludGVycG9sYXRlZF9aW1tnXV1bWyJuZXdfdmFsdWVzIl1dIDwtIGg1cmVhZChmaWxlLCAiWl9wcmVkaWN0aW9ucy9uZXdfdmFsdWVzIikKICAjICAgICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiTmV3IHZhbHVlcyBvZiBaIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQogICMgICAgIH0KICAjICAgfQogICMgfQogICMgb2JqZWN0QGludGVycG9sYXRlZF9aIDwtIGludGVycG9sYXRlZF9aCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIGV4cGVjdGF0aW9ucyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGV4cGVjdGF0aW9ucyA8LSBsaXN0KCkKICBub2RlX25hbWVzIDwtIGg1bHMub3V0W2g1bHMub3V0JGdyb3VwPT0iL2V4cGVjdGF0aW9ucyIsIm5hbWUiXQoKICBpZiAodmVyYm9zZSkgbWVzc2FnZShwYXN0ZTAoIkxvYWRpbmcgZXhwZWN0YXRpb25zIGZvciAiLCBsZW5ndGgobm9kZV9uYW1lcyksICIgbm9kZXMuLi4iKSkKCiAgaWYgKCJBbHBoYVciICVpbiUgbm9kZV9uYW1lcykKICAgIGV4cGVjdGF0aW9uc1tbIkFscGhhVyJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9BbHBoYVciKVt2aWV3X25hbWVzXQogIGlmICgiQWxwaGFaIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJBbHBoYVoiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvQWxwaGFaIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJTaWdtYSIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siU2lnbWEiXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvU2lnbWEiKQogIGlmICgiWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siWiJdXSA8LSBoNXJlYWQoZmlsZSwgImV4cGVjdGF0aW9ucy9aIilbZ3JvdXBfbmFtZXNdCiAgaWYgKCJXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJXIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1ciKVt2aWV3X25hbWVzXQogIGlmICgiVGhldGFXIiAlaW4lIG5vZGVfbmFtZXMpCiAgICBleHBlY3RhdGlvbnNbWyJUaGV0YVciXV0gPC0gaDVyZWFkKGZpbGUsICJleHBlY3RhdGlvbnMvVGhldGFXIilbdmlld19uYW1lc10KICBpZiAoIlRoZXRhWiIgJWluJSBub2RlX25hbWVzKQogICAgZXhwZWN0YXRpb25zW1siVGhldGFaIl1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RoZXRhWiIpW2dyb3VwX25hbWVzXQogICMgaWYgKCJUYXUiICVpbiUgbm9kZV9uYW1lcykKICAjICAgZXhwZWN0YXRpb25zW1siVGF1Il1dIDwtIGg1cmVhZChmaWxlLCAiZXhwZWN0YXRpb25zL1RhdSIpCiAgCiAgb2JqZWN0QGV4cGVjdGF0aW9ucyA8LSBleHBlY3RhdGlvbnMKCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCBtb2RlbCBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJMb2FkaW5nIG1vZGVsIG9wdGlvbnMuLi4iKQoKICB0cnlDYXRjaCggewogICAgb2JqZWN0QG1vZGVsX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ21vZGVsX29wdGlvbnMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIk1vZGVsIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCgogICMgQ29udmVydCBUcnVlL0ZhbHNlIHN0cmluZ3MgdG8gbG9naWNhbCB2YWx1ZXMKICBmb3IgKGkgaW4gbmFtZXMob2JqZWN0QG1vZGVsX29wdGlvbnMpKSB7CiAgICBpZiAob2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPT0gIkZhbHNlIiB8fCBvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSA9PSAiVHJ1ZSIpIHsKICAgICAgb2JqZWN0QG1vZGVsX29wdGlvbnNbaV0gPC0gYXMubG9naWNhbChvYmplY3RAbW9kZWxfb3B0aW9uc1tpXSkKICAgIH0gZWxzZSB7CiAgICAgIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldIDwtIG9iamVjdEBtb2RlbF9vcHRpb25zW2ldCiAgICB9CiAgfQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIyBMb2FkIHRyYWluaW5nIG9wdGlvbnMgYW5kIHN0YXRpc3RpY3MgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCiAgaWYgKHZlcmJvc2UpIG1lc3NhZ2UoIkxvYWRpbmcgdHJhaW5pbmcgb3B0aW9ucyBhbmQgc3RhdGlzdGljcy4uLiIpCgogICMgTG9hZCB0cmFpbmluZyBvcHRpb25zCiAgaWYgKGxlbmd0aChvYmplY3RAdHJhaW5pbmdfb3B0aW9ucykgPT0gMCkgewogICAgdHJ5Q2F0Y2goIHsKICAgICAgb2JqZWN0QHRyYWluaW5nX29wdGlvbnMgPC0gYXMubGlzdChoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX29wdHMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICAgIH0sIGVycm9yID0gZnVuY3Rpb24oeCkgeyBwcmludCgiVHJhaW5pbmcgb3B0cyBub3QgZm91bmQsIG5vdCBsb2FkaW5nIGl0Li4uIikgfSkKICB9CgogICMgTG9hZCB0cmFpbmluZyBzdGF0aXN0aWNzCiAgdHJ5Q2F0Y2goIHsKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICAgIG9iamVjdEB0cmFpbmluZ19zdGF0cyA8LSBoNXJlYWQoZmlsZSwgJ3RyYWluaW5nX3N0YXRzJywgcmVhZC5hdHRyaWJ1dGVzID0gVFJVRSkKICB9LCBlcnJvciA9IGZ1bmN0aW9uKHgpIHsgcHJpbnQoIlRyYWluaW5nIHN0YXRzIG5vdCBmb3VuZCwgbm90IGxvYWRpbmcgaXQuLi4iKSB9KQoKICAjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIExvYWQgY292YXJpYXRlcyBvcHRpb25zICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKICAjIAogICMgaWYgKGFueShncmVwbCgiY292X3NhbXBsZXMiLCBoNWxzLm91dCRncm91cCkpKSB7IAogICMgICBpZiAoaXNUUlVFKHZlcmJvc2UpKSBtZXNzYWdlKCJMb2FkaW5nIGNvdmFyaWF0ZXMgb3B0aW9ucy4uLiIpCiAgIyAgIHRyeUNhdGNoKCB7CiAgIyAgICAgb2JqZWN0QG1lZmlzdG9fb3B0aW9ucyA8LSBhcy5saXN0KGg1cmVhZChmaWxlLCAnc21vb3RoX29wdHMnLCByZWFkLmF0dHJpYnV0ZXMgPSBUUlVFKSkKICAjICAgfSwgZXJyb3IgPSBmdW5jdGlvbih4KSB7IHByaW50KCJDb3ZhcmlhdGVzIG9wdGlvbnMgbm90IGZvdW5kLCBub3QgbG9hZGluZyBpdC4uLiIpIH0pCiAgIyAgIAogICMgICAjIENvbnZlcnQgVHJ1ZS9GYWxzZSBzdHJpbmdzIHRvIGxvZ2ljYWwgdmFsdWVzCiAgIyAgIGZvciAoaSBpbiBuYW1lcyhvYmplY3RAbWVmaXN0b19vcHRpb25zKSkgewogICMgICAgIGlmIChvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldID09ICJGYWxzZSIgfCBvYmplY3RAbWVmaXN0b19vcHRpb25zW2ldID09ICJUcnVlIikgewogICMgICAgICAgb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXSA8LSBhcy5sb2dpY2FsKG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0pCiAgIyAgICAgfSBlbHNlIHsKICAjICAgICAgIG9iamVjdEBtZWZpc3RvX29wdGlvbnNbaV0gPC0gb2JqZWN0QG1lZmlzdG9fb3B0aW9uc1tpXQogICMgICAgIH0KICAjICAgfQogICMgICAKICAjIH0KICAjIAogIAogICAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgIyMgTG9hZCB2YXJpYW5jZSBleHBsYWluZWQgZXN0aW1hdGVzICMjCiAgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiAgCiAgaWYgKCJ2YXJpYW5jZV9leHBsYWluZWQiICVpbiUgaDVscy5vdXQkbmFtZSkgewogICAgcjJfbGlzdCA8LSBsaXN0KAogICAgICByMl90b3RhbCA9IGg1cmVhZChmaWxlLCAidmFyaWFuY2VfZXhwbGFpbmVkL3IyX3RvdGFsIilbZ3JvdXBfbmFtZXNdLAogICAgICByMl9wZXJfZmFjdG9yID0gaDVyZWFkKGZpbGUsICJ2YXJpYW5jZV9leHBsYWluZWQvcjJfcGVyX2ZhY3RvciIpW2dyb3VwX25hbWVzXQogICAgKQogICAgb2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dIDwtIHIyX2xpc3QKICB9CiAgCiAgIyBIYWNrIHRvIGZpeCB0aGUgcHJvYmxlbXMgd2hlcmUgdmFyaWFuY2UgZXhwbGFpbmVkIHZhbHVlcyByYW5nZSBmcm9tIDAgdG8gMSAoJSkKICBpZiAobWF4KHNhcHBseShvYmplY3RAY2FjaGUkdmFyaWFuY2VfZXhwbGFpbmVkJHIyX3RvdGFsLG1heCxuYS5ybT1UUlVFKSxuYS5ybT1UUlVFKTwxKSB7CiAgICBmb3IgKG0gaW4gMTpsZW5ndGgodmlld19uYW1lcykpIHsKICAgICAgZm9yIChnIGluIDE6bGVuZ3RoKGdyb3VwX25hbWVzKSkgewogICAgICAgIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfdG90YWxbW2ddXVtbbV1dCiAgICAgICAgb2JqZWN0QGNhY2hlJHZhcmlhbmNlX2V4cGxhaW5lZCRyMl9wZXJfZmFjdG9yW1tnXV1bLG1dIDwtIDEwMCAqIG9iamVjdEBjYWNoZSR2YXJpYW5jZV9leHBsYWluZWQkcjJfcGVyX2ZhY3RvcltbZ11dWyxtXQogICAgICB9CiAgICB9CiAgfQogIAogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIFNwZWNpZnkgZGltZW5zaW9uYWxpdGllcyAjIwogICMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogIAogICMgU3BlY2lmeSBkaW1lbnNpb25hbGl0eSBvZiB0aGUgZGF0YQogIG9iamVjdEBkaW1lbnNpb25zW1siTSJdXSA8LSBsZW5ndGgoZGF0YSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygdmlld3MKICBvYmplY3RAZGltZW5zaW9uc1tbIkciXV0gPC0gbGVuZ3RoKGRhdGFbWzFdXSkgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIGdyb3VwcwogIG9iamVjdEBkaW1lbnNpb25zW1siTiJdXSA8LSBzYXBwbHkoZGF0YVtbMV1dLCBuY29sKSAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2Ygc2FtcGxlcyAocGVyIGdyb3VwKQogIG9iamVjdEBkaW1lbnNpb25zW1siRCJdXSA8LSBzYXBwbHkoZGF0YSwgZnVuY3Rpb24oZSkgbnJvdyhlW1sxXV0pKSAgIyBudW1iZXIgb2YgZmVhdHVyZXMgKHBlciB2aWV3KQogICMgb2JqZWN0QGRpbWVuc2lvbnNbWyJDIl1dIDwtIG5yb3coY292YXJpYXRlc1tbMV1dKSAgICAgICAgICAgICAgICAgICAgICAgICMgbnVtYmVyIG9mIGNvdmFyaWF0ZXMKICBvYmplY3RAZGltZW5zaW9uc1tbIksiXV0gPC0gbmNvbChvYmplY3RAZXhwZWN0YXRpb25zJFpbWzFdXSkgICAgICAgICMgbnVtYmVyIG9mIGZhY3RvcnMKICAKICAjIEFzc2lnbiBzYW1wbGUgYW5kIGZlYXR1cmUgbmFtZXMgKHNsb3cgZm9yIGxhcmdlIG1hdHJpY2VzKQogIGlmICh2ZXJib3NlKSBtZXNzYWdlKCJBc3NpZ25pbmcgbmFtZXMgdG8gdGhlIGRpZmZlcmVudCBkaW1lbnNpb25zLi4uIikKCiAgIyBDcmVhdGUgZGVmYXVsdCBmZWF0dXJlcyBuYW1lcyBpZiB0aGV5IGFyZSBudWxsCiAgaWYgKGlzLm51bGwoZmVhdHVyZV9uYW1lcykpIHsKICAgIHByaW50KCJGZWF0dXJlcyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogZmVhdHVyZTFfdmlldzEsIC4uLiwgZmVhdHVyZURfdmlld00iKQogICAgZmVhdHVyZV9uYW1lcyA8LSBsYXBwbHkoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIk0iXV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24obSkgc3ByaW50ZigiZmVhdHVyZSVkX3ZpZXdfJmQiLCBhcy5jaGFyYWN0ZXIoc2VxX2xlbihvYmplY3RAZGltZW5zaW9uc1tbIkQiXV1bbV0pKSwgbSkpCiAgfSBlbHNlIHsKICAgICMgQ2hlY2sgZHVwbGljYXRlZCBmZWF0dXJlcyBuYW1lcwogICAgYWxsX25hbWVzIDwtIHVubmFtZSh1bmxpc3QoZmVhdHVyZV9uYW1lcykpCiAgICBkdXBsaWNhdGVkX25hbWVzIDwtIHVuaXF1ZShhbGxfbmFtZXNbZHVwbGljYXRlZChhbGxfbmFtZXMpXSkKICAgIGlmIChsZW5ndGgoZHVwbGljYXRlZF9uYW1lcyk+MCkgCiAgICAgIHdhcm5pbmcoIlRoZXJlIGFyZSBkdXBsaWNhdGVkIGZlYXR1cmVzIG5hbWVzIGFjcm9zcyBkaWZmZXJlbnQgdmlld3MuIFdlIHdpbGwgYWRkIHRoZSBzdWZmaXggKl92aWV3KiBvbmx5IGZvciB0aG9zZSBmZWF0dXJlcyAKICAgICAgICAgICAgRXhhbXBsZTogaWYgeW91IGhhdmUgYm90aCBUUDUzIGluIG1STkEgYW5kIG11dGF0aW9uIGRhdGEgaXQgd2lsbCBiZSByZW5hbWVkIHRvIFRQNTNfbVJOQSwgVFA1M19tdXRhdGlvbiIpCiAgICBmb3IgKG0gaW4gbmFtZXMoZmVhdHVyZV9uYW1lcykpIHsKICAgICAgdG1wIDwtIHdoaWNoKGZlYXR1cmVfbmFtZXNbW21dXSAlaW4lIGR1cGxpY2F0ZWRfbmFtZXMpCiAgICAgIGlmIChsZW5ndGgodG1wKT4wKSBmZWF0dXJlX25hbWVzW1ttXV1bdG1wXSA8LSBwYXN0ZShmZWF0dXJlX25hbWVzW1ttXV1bdG1wXSwgbSwgc2VwPSJfIikKICAgIH0KICB9CiAgZmVhdHVyZXNfbmFtZXMob2JqZWN0KSA8LSBmZWF0dXJlX25hbWVzCiAgCiAgIyBDcmVhdGUgZGVmYXVsdCBzYW1wbGVzIG5hbWVzIGlmIHRoZXkgYXJlIG51bGwKICBpZiAoaXMubnVsbChzYW1wbGVfbmFtZXMpKSB7CiAgICBwcmludCgiU2FtcGxlcyBuYW1lcyBub3QgZm91bmQsIGdlbmVyYXRpbmcgZGVmYXVsdDogc2FtcGxlMSwgLi4uLCBzYW1wbGVOIikKICAgIHNhbXBsZV9uYW1lcyA8LSBsYXBwbHkob2JqZWN0QGRpbWVuc2lvbnNbWyJOIl1dLCBmdW5jdGlvbihuKSBwYXN0ZTAoInNhbXBsZSIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG4pKSkpCiAgfQogIHNhbXBsZXNfbmFtZXMob2JqZWN0KSA8LSBzYW1wbGVfbmFtZXMKCiAgIyBBZGQgY292YXJpYXRlcyBuYW1lcwogICMgaWYoIWlzLm51bGwob2JqZWN0QGNvdmFyaWF0ZXMpKXsKICAjICAgIyBDcmVhdGUgZGVmYXVsdCBjb3ZhcmlhdGVzIG5hbWVzIGlmIHRoZXkgYXJlIG51bGwKICAjICAgaWYgKGlzLm51bGwoY292YXJpYXRlX25hbWVzKSkgewogICMgICAgIHByaW50KCJDb3ZhcmlhdGUgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGNvdmFyaWF0ZTEsIC4uLiwgY292YXJpYXRlQyIpCiAgIyAgICAgY292YXJpYXRlX25hbWVzIDwtIHBhc3RlMCgic2FtcGxlIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJDIl1dKSkpCiAgIyAgIH0KICAjICAgY292YXJpYXRlc19uYW1lcyhvYmplY3QpIDwtIGNvdmFyaWF0ZV9uYW1lcwogICMgfQogIAogICMgU2V0IHZpZXdzIG5hbWVzCiAgaWYgKGlzLm51bGwobmFtZXMob2JqZWN0QGRhdGEpKSkgewogICAgcHJpbnQoIlZpZXdzIG5hbWVzIG5vdCBmb3VuZCwgZ2VuZXJhdGluZyBkZWZhdWx0OiB2aWV3MSwgLi4uLCB2aWV3TSIpCiAgICB2aWV3X25hbWVzIDwtIHBhc3RlMCgidmlldyIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siTSJdXSkpKQogIH0KICB2aWV3c19uYW1lcyhvYmplY3QpIDwtIHZpZXdfbmFtZXMKICAKICAjIFNldCBncm91cHMgbmFtZXMKICBpZiAoaXMubnVsbChuYW1lcyhvYmplY3RAZGF0YVtbMV1dKSkpIHsKICAgIHByaW50KCJHcm91cHMgbmFtZXMgbm90IGZvdW5kLCBnZW5lcmF0aW5nIGRlZmF1bHQ6IGdyb3VwMSwgLi4uLCBncm91cEciKQogICAgZ3JvdXBfbmFtZXMgPC0gcGFzdGUwKCJncm91cCIsIGFzLmNoYXJhY3RlcihzZXFfbGVuKG9iamVjdEBkaW1lbnNpb25zW1siRyJdXSkpKQogIH0KICBncm91cHNfbmFtZXMob2JqZWN0KSA8LSBncm91cF9uYW1lcwogIAogICMgU2V0IGZhY3RvcnMgbmFtZXMKICBmYWN0b3JzX25hbWVzKG9iamVjdCkgIDwtIHBhc3RlMCgiRmFjdG9yIiwgYXMuY2hhcmFjdGVyKHNlcV9sZW4ob2JqZWN0QGRpbWVuc2lvbnNbWyJLIl1dKSkpCiAgCiAgIyMjIyMjIyMjIyMjIyMjIyMjIwogICMjIFBhcnNlIGZhY3RvcnMgIyMKICAjIyMjIyMjIyMjIyMjIyMjIyMjCiAgCiAgIyBDYWxjdWxhdGUgdmFyaWFuY2UgZXhwbGFpbmVkIGVzdGltYXRlcyBwZXIgZmFjdG9yCiAgaWYgKGlzLm51bGwob2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dKSkgewogICAgb2JqZWN0QGNhY2hlW1sidmFyaWFuY2VfZXhwbGFpbmVkIl1dIDwtIGNhbGN1bGF0ZV92YXJpYW5jZV9leHBsYWluZWQob2JqZWN0KQogIH0gCiAgCiAgIyBSZW1vdmUgaW5hY3RpdmUgZmFjdG9ycwogIGlmIChyZW1vdmVfaW5hY3RpdmVfZmFjdG9ycykgewogICAgcjIgPC0gcm93U3Vtcyhkby5jYWxsKCdjYmluZCcsIGxhcHBseShvYmplY3RAY2FjaGVbWyJ2YXJpYW5jZV9leHBsYWluZWQiXV0kcjJfcGVyX2ZhY3Rvciwgcm93U3VtcywgbmEucm09VFJVRSkpKQogICAgdmFyLnRocmVzaG9sZCA8LSAwLjAwMDEKICAgIGlmIChhbGwocjIgPCB2YXIudGhyZXNob2xkKSkgewogICAgICB3YXJuaW5nKHNwcmludGYoIkFsbCAlcyBmYWN0b3JzIHdlcmUgZm91bmQgdG8gZXhwbGFpbiBsaXR0bGUgb3Igbm8gdmFyaWFuY2Ugc28gcmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMgb3B0aW9uIGhhcyBiZWVuIGRpc2FibGVkLiIsIGxlbmd0aChyMikpKQogICAgfSBlbHNlIGlmIChhbnkocjIgPCB2YXIudGhyZXNob2xkKSkgewogICAgICBvYmplY3QgPC0gc3Vic2V0X2ZhY3RvcnMob2JqZWN0LCB3aGljaChyMj49dmFyLnRocmVzaG9sZCkpCiAgICAgIG1lc3NhZ2Uoc3ByaW50ZigiJXMgZmFjdG9ycyB3ZXJlIGZvdW5kIHRvIGV4cGxhaW4gbm8gdmFyaWFuY2UgYW5kIHRoZXkgd2VyZSByZW1vdmVkIGZvciBkb3duc3RyZWFtIGFuYWx5c2lzLiBZb3UgY2FuIGRpc2FibGUgdGhpcyBvcHRpb24gYnkgc2V0dGluZyBsb2FkX21vZGVsKC4uLiwgcmVtb3ZlX2luYWN0aXZlX2ZhY3RvcnMgPSBGQUxTRSkiLCBzdW0ocjIgPCB2YXIudGhyZXNob2xkKSkpCiAgICB9CiAgfQogIAogICMgW0RvbmUgaW4gbW9mYXB5Ml0gU29ydCBmYWN0b3JzIGJ5IHRvdGFsIHZhcmlhbmNlIGV4cGxhaW5lZAogIGlmIChzb3J0X2ZhY3RvcnMgJiYgb2JqZWN0QGRpbWVuc2lvbnMkSz4xKSB7CgogICAgIyBTYW5pdHkgY2hlY2tzCiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmUtb3JkZXJpbmcgZmFjdG9ycyBieSB0aGVpciB2YXJpYW5jZSBleHBsYWluZWQuLi4iKQoKICAgICMgQ2FsY3VsYXRlIHZhcmlhbmNlIGV4cGxhaW5lZCBwZXIgZmFjdG9yIGFjcm9zcyBhbGwgdmlld3MKICAgIHIyIDwtIHJvd1N1bXMoc2FwcGx5KG9iamVjdEBjYWNoZVtbInZhcmlhbmNlX2V4cGxhaW5lZCJdXSRyMl9wZXJfZmFjdG9yLCBmdW5jdGlvbihlKSByb3dTdW1zKGUsIG5hLnJtID0gVFJVRSkpKQogICAgb3JkZXJfZmFjdG9ycyA8LSBjKG5hbWVzKHIyKVtvcmRlcihyMiwgZGVjcmVhc2luZyA9IFRSVUUpXSkKCiAgICAjIHJlLW9yZGVyIGZhY3RvcnMKICAgIG9iamVjdCA8LSBzdWJzZXRfZmFjdG9ycyhvYmplY3QsIG9yZGVyX2ZhY3RvcnMpCiAgfQoKICAjIE1hc2sgb3V0bGllcnMKICBpZiAocmVtb3ZlX291dGxpZXJzKSB7CiAgICBpZiAodmVyYm9zZSkgbWVzc2FnZSgiUmVtb3Zpbmcgb3V0bGllcnMuLi4iKQogICAgb2JqZWN0IDwtIC5kZXRlY3Rfb3V0bGllcnMob2JqZWN0KQogIH0KICAKICAjIE1hc2sgaW50ZXJjZXB0cyBmb3Igbm9uLUdhdXNzaWFuIGRhdGEKICBpZiAoYW55KG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkgewogICAgZm9yIChtIGluIG5hbWVzKHdoaWNoKG9iamVjdEBtb2RlbF9vcHRpb25zJGxpa2VsaWhvb2RzIT0iZ2F1c3NpYW4iKSkpIHsKICAgICAgZm9yIChnIGluIG5hbWVzKG9iamVjdEBpbnRlcmNlcHRzW1ttXV0pKSB7CiAgICAgICAgb2JqZWN0QGludGVyY2VwdHNbW21dXVtbZ11dIDwtIE5BCiAgICAgIH0KICAgIH0KICB9CgogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgIyMgUXVhbGl0eSBjb250cm9scyAjIwogICMgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwogICMgCiAgIyBpZiAodmVyYm9zZSkgbWVzc2FnZSgiRG9pbmcgcXVhbGl0eSBjb250cm9sLi4uIikKICAjIG9iamVjdCA8LSAucXVhbGl0eV9jb250cm9sKG9iamVjdCwgdmVyYm9zZSA9IHZlcmJvc2UpCiAgIyAKICByZXR1cm4ob2JqZWN0KQp9Cgptb2ZhX3RyYWluZWQgPC0gbG9hZF9tb2RlbChvdXRmaWxlKQoKc2FtcGxlc19uYW1lcyhtb2ZhX3RyYWluZWQpIDwtIHNhbXBsZXNfbmFtZXMob2JqZWN0KQpzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkKcm93bmFtZXMoc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSA8LSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZClbWyJzYW1wbGUiXV0KCgpgYGAKCiMjIyBQcnVuZSBmYWN0b3JzCgojIyMjIFZpc3VhbGl6ZSB2YXJpYW5jZSBleHBsYWluZWQgYnkgZmFjdG9ycwoKYGBge3J9CmdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBhcy5kYXRhLmZyYW1lID0gVFJVRSwgKVtbMV1dICU+JQogIGRwbHlyOjptdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHM9cmV2KGFubm9fb3JkZXIpKSkgJT4lIAogIGdncGxvdChhZXMoZmFjdG9yLGdyb3VwLCBmaWxsPXZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSkpCgpnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgYXMuZGF0YS5mcmFtZSA9IFRSVUUsIClbWzJdXSAlPiUKICBkcGx5cjo6bXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPWFubm9fb3JkZXIpKSAlPiUKICBnZ3Bsb3QoYWVzKGdyb3VwLCB2YWx1ZSkpICsKICBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIHlsYWIoIlZhci4gKCUpIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplPTE0KQpgYGAKCmBgYHtyfQpwbG90X2ZhY3Rvcl9jb3IobW9mYV90cmFpbmVkLCBtZXRob2QgPSAic3BlYXJtYW4iKQoKIyMgQ29ycmVsYXRpb24gd2l0aCBwcmluY2lwYWwgY29tcG9uZW50cwpwY3MgPC0gcmVkdWNlZERpbShzY2UpCmZjdHJzIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCkgJT4lCiAgcHVycnI6OnJlZHVjZShyYmluZCkKCmNvcnJwbG90Ojpjb3JycGxvdChjb3IocGNzLCBmY3Ryc1tyb3duYW1lcyhwY3MpLF0pKQpgYGAKCiMjIyMgSWRlbnRpZnkgdGVjaG5pY2FsIGZhY3RvcnMKCkRvIGZhY3RvcnMgcmVsYXRlIHRvIHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gcHNldWRvYnVsaz8KYGBge3IsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00fQpuX2NlbGxzIDwtIG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhWywnbl9jZWxscycsIGRyb3A9RkFMU0VdClogPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkKQpaIDwtIHB1cnJyOjpyZWR1Y2UoWiwgcmJpbmQpCmJhcnBsb3QoY29yKG5fY2VsbHMsIFopKQpgYGAKCkRpc3Rpbmd1aXNoIHRlY2huaWNhbCBmYWN0b3JzIGJ5IHdlaWdodCBzcGFyc2l0eQoKYGBge3J9CmdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgYWJzPVRSVUUsIHNjYWxlID0gRkFMU0UsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoZmFjdG9yKSAlPiUKICBkcGx5cjo6bXV0YXRlKHZhbHVlPSh2YWx1ZSAtIG1pbih2YWx1ZSkpLyhtYXgodmFsdWUpLSBtaW4odmFsdWUpKSwgcmFuaz1yYW5rKHZhbHVlKSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShmcmFjX3plcm9zPXN1bSh2YWx1ZSA8IDAuMDUpL2RwbHlyOjpuKCkpICU+JQogIGdncGxvdChhZXMoZmFjdG9yLCBmcmFjX3plcm9zKSkgKwogIGdlb21fY29sKCkgKwogIGNvb3JkX2ZsaXAoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBjb2xvcj0icmVkIikKYGBgCgpgYGB7cn0KZXhjbHVkZV9mYWN0b3JzIDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgYWJzPVRSVUUsIHNjYWxlID0gRkFMU0UsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoZmFjdG9yKSAlPiUKICBkcGx5cjo6bXV0YXRlKHZhbHVlPSh2YWx1ZSAtIG1pbih2YWx1ZSkpLyhtYXgodmFsdWUpLSBtaW4odmFsdWUpKSwgcmFuaz1yYW5rKHZhbHVlKSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShmcmFjX3plcm9zPXN1bSh2YWx1ZSA8IDAuMDUpL2RwbHlyOjpuKCkpICU+JQogIGRwbHlyOjpmaWx0ZXIoZnJhY196ZXJvcyA8IDAuNSkgJT4lCiAgZHBseXI6OnB1bGwoZmFjdG9yKQpgYGAKCmBgYHtyfQpoaWdoX3IyX2dyb3Vwc19kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDUsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogIGRwbHlyOjpmaWx0ZXIoIWZhY3RvciAlaW4lIGV4Y2x1ZGVfZmFjdG9ycykgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGdyb3VwKSAlPiUKICBkcGx5cjo6bXV0YXRlKHRvdF92YXI9c3VtKHZhbHVlKSkgJT4lCiAgZHBseXI6OnVuZ3JvdXAoKSAlPiUKICBkcGx5cjo6YXJyYW5nZSh0b3RfdmFyKSAlPiUKICBkcGx5cjo6bXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzID0gdW5pcXVlKGdyb3VwKSkpICU+JQogIGRwbHlyOjptdXRhdGUodmFsdWU9aWZlbHNlKHZhbHVlIDwgMiwgTkEsIHZhbHVlKSkgJT4lCiAgZHBseXI6OmZpbHRlcighaXMubmEodmFsdWUpKSAKCmhpZ2hfcjJfZ3JvdXBzX2RmICU+JQogIGdncGxvdChhZXMoZmFjdG9yLGdyb3VwLCBmaWxsPXZhbHVlKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50bihjb2xvcnM9YygiZ3JheTk3IiwiZGFya2JsdWUiKSwgZ3VpZGU9ImNvbG9yYmFyIikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSAKICAKYGBgCgojIyMjIEZpbmQgZmFjdG9ycyB0aGF0IHNlcGFyYXRlIHBzZXVkb2J1bGtzIGJ5IHRpc3N1ZQoKQ2FsY3VsYXRpbmcgQWRqdXN0ZWQgTXV0dWFsIEluZm9ybWF0aW9uIGJldHdlZW4gb3JnYW4gaWRlbnRpdHkgYW5kIGNsdXN0ZXJpbmcgb2YgcHNldWRvYnVsa3MgYmFzZWQgb24gZmFjdG9yIHZhbHVlcwoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD02fQpjYWxjX29yZ2FuX0FNSSA8LSBmdW5jdGlvbihmLCBnKXsKICBkbWF0IDwtIGRpc3QoZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgZ3JvdXBzID0gZylbWzFdXSkgCiAgaGNsIDwtIGhjbHVzdChkbWF0KQogIG5fb3JnYW5zIDwtIGxlbmd0aCh1bmlxdWUoc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpW3Jvd25hbWVzKGFzLm1hdHJpeChkbWF0KSksJ29yZ2FuJ10pKQogIGhjbF9kZiA8LSBkYXRhLmZyYW1lKGNsdXN0PWN1dHJlZShoY2wsIGs9bl9vcmdhbnMpKSAlPiUKICAgIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJzYW1wbGUiKSAlPiUKICAgIGRwbHlyOjpsZWZ0X2pvaW4oc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpKSAKICBvcmdhbl9BTUkgPC0gYXJpY29kZTo6QU1JKGhjbF9kZiRjbHVzdCwgYXMubnVtZXJpYyhoY2xfZGYkb3JnYW4pKQogIHJldHVybihvcmdhbl9BTUkpCn0KCiMjIENhbGMgYWRqdXN0ZWQgbXV0dWFsIGluZm8gZm9yIGVhY2ggZmFjdG9yCkFNSXMgPC0gc2FwcGx5KDE6bnJvdyhoaWdoX3IyX2dyb3Vwc19kZiksIGZ1bmN0aW9uKGkpIGNhbGNfb3JnYW5fQU1JKGhpZ2hfcjJfZ3JvdXBzX2RmJGZhY3RvcltpXSwgaGlnaF9yMl9ncm91cHNfZGYkZ3JvdXBbaV0pKQoKIyMgQ2FsYyBhZGp1c3RlZCBtdXR1YWwgaW5mbyBmb3IgYWxsIGZhY3RvcnMgdGhhdCBleHBsYW4gPiAyJSB2YXJpYW5jZSAKZ3JvdXBzIDwtIGFzLmNoYXJhY3Rlcih1bmlxdWUoaGlnaF9yMl9ncm91cHNfZGYkZ3JvdXApKQpjdF9BTUlzIDwtIHNhcHBseShncm91cHMsIGZ1bmN0aW9uKGcpIGNhbGNfb3JnYW5fQU1JKGhpZ2hfcjJfZ3JvdXBzX2RmJGZhY3RvcltoaWdoX3IyX2dyb3Vwc19kZiRncm91cCA9PSBnXSwgZykpCgpBTUlfcGwgPC0gZGF0YS5mcmFtZShjdF9BTUlzKSAlPiUKICBkcGx5cjo6YXJyYW5nZShjdF9BTUlzKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiY2VsbHR5cGUiKSAlPiUKICAjIGRwbHlyOjptdXRhdGUoY2VsbHR5cGU9ZmFjdG9yKHJvd25hbWUsIGxldmVscz11bmlxdWUocm93bmFtZSkpKSAlPiUKZHBseXI6Om11dGF0ZShjZWxsdHlwZT1mYWN0b3IoY2VsbHR5cGUsIGxldmVscz1yZXYoYW5ub19vcmRlcikpKSAlPiUKICBnZ3Bsb3QoYWVzKGN0X0FNSXMsIGNlbGx0eXBlKSkgKwogIGdlb21fY29sKCkgKwogIHhsYWIoIlRvdGFsIE9yZ2FuIEFNSSIpICsKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZSA9IDE2KQogIApBTUlfZl9wbCA8LSBoaWdoX3IyX2dyb3Vwc19kZiAlPiUKICBkcGx5cjo6bXV0YXRlKG9yZ19BTUk9QU1JcykgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz1sZXZlbHMoQU1JX3BsJGRhdGEkY2VsbHR5cGUpKSkgJT4lCiAgZ2dwbG90KGFlcyhmYWN0b3IsIGdyb3VwLCBmaWxsPW9yZ19BTUkpKSArCiAgZ2VvbV90aWxlKGNvbG9yPSdibGFjaycpICsKICAjIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG9ycz1jKCJncmF5OTciLCJyZWQiKSwgbmFtZT0iT3JnYW4gQWRqLiBNdXR1YWwgSW5mbyIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb249J21hZ21hJykgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplID0gMTYpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSkpICAgCgpBTUlfZl9wbCArIChBTUlfcGwgKyByZW1vdmVfeV9heGlzKCkpICsKICBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiLCB3aWR0aHMgPSBjKDgsMykpCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoPTd9CiMjIFNhdmUgaW5mbyBvbiBNSQpoaWdoX3IyX2dyb3Vwc19kZiA8LSBoaWdoX3IyX2dyb3Vwc19kZiAlPiUKICBkcGx5cjo6bXV0YXRlKG9yZ19BTUk9QU1JcykgCgojIyBGaXggbG9uZyBuYW1lcyBmb3IgcGxvdHRpbmcKYWxsX2dyb3VwcyA8LSBuYW1lcyhnZXRfZGF0YShtb2ZhX3RyYWluZWQpW1sxXV0pCmdyb3VwX2xhYmVsbGVyIDwtIGFsbF9ncm91cHMgJT4lCiAgc3RyX3JlcGxhY2VfYWxsKCJfIiwgIiAiKSAlPiUKICB7aWZlbHNlKG5jaGFyKC4pID4gMjAsIHN0cl9yZXBsYWNlKC4sICIgIiwgIlxuIiksIC4pfSAlPiUKICBzZXROYW1lcyhhbGxfZ3JvdXBzKQoKQU1JX3BsX2RmIDwtIGhpZ2hfcjJfZ3JvdXBzX2RmICU+JQogIGRwbHlyOjpncm91cF9ieShncm91cCkgJT4lCiAgZHBseXI6Om11dGF0ZShtZWFuX0FNST1tYXgob3JnX0FNSSkpICU+JQogIGRwbHlyOjp1bmdyb3VwKCkgJT4lCiAgZHBseXI6OmFycmFuZ2UobWVhbl9BTUkpICU+JQogIGRwbHlyOjptdXRhdGUoZ3JvdXA9Z3JvdXBfbGFiZWxsZXJbYXMuY2hhcmFjdGVyKGdyb3VwKV0pICU+JQogIGRwbHlyOjptdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHM9dW5pcXVlKGdyb3VwKSkpIAoKQU1JX3BsX2RmICU+JQogIGdncGxvdChhZXMob3JnX0FNSSwgZ3JvdXApKSArCiAgZ2VvbV9wb2ludChhZXMoZmlsbD12YWx1ZSksIHNpemU9Mywgc2hhcGU9MjEpICsKICBnZ3JlcGVsOjpnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPXN0cl9yZW1vdmUoZmFjdG9yLCAiRmFjdG9yIikpLCBjb2xvcj0iYmxhY2siLCBmb3JjZSA9IDAuMSwgZGlyZWN0aW9uID0gJ3gnLAogICAgICAgICAgICAgICAgICAgICAgICAgICBudWRnZV95ICAgICAgICAgICA9IDAuNCwKICAgIGhqdXN0ICAgICAgICAgICAgID0gMCkgKwogIHhsYWIoIkFkai4gTXV0dWFsIEluZm9ybWF0aW9uIC0gT3JnYW4gIikgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKCJ3aGl0ZSIsICJyZWQiKSwgbmFtZT0iJSB2YXIuIGV4cGxhaW5lZCIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNSkKCgpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTh9CmZvciAoZmFjdCBpbiBhcy5jaGFyYWN0ZXIodW5pcXVlKEFNSV9wbF9kZiRmYWN0b3IpKSl7CiAgcCA8LSBBTUlfcGxfZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKG9yZ19BTUksIGdyb3VwKSkgKwogICAgZ2VvbV9wb2ludChmaWxsPSJncmV5Iiwgc2l6ZT0yLCBzaGFwZT0yMSwgY29sb3I9ImdyZXkiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSAuICU+JSBkcGx5cjo6ZmlsdGVyKGZhY3Rvcj09ZmFjdCksCiAgICAgICAgICAgICAgICAgYWVzKGZpbGw9dmFsdWUpLCBzaXplPTMsIHNoYXBlPTIxKSArCiAgICB4bGFiKCJBZGouIE11dHVhbCBJbmZvcm1hdGlvbiAtIE9yZ2FuICIpICsKICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnRuKGNvbG91cnMgPSBjKCJ3aGl0ZSIsICJyZWQiKSwgbmFtZT0iJSB2YXIuIGV4cGxhaW5lZCIpICsKICAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE1KSArCiAgICBnZ3RpdGxlKGZhY3QpCiAgcHJpbnQocCkKICB9CmBgYAoKCiMjIyBGYWN0b3IgSUQgcGxvdHMKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CnBsb3RfZmFjdG9yX29yZGVyZWQgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmKXsKICBmYWN0b3JfZGYgPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpICU+JQogICAgICBtdXRhdGUob3JnYW4gPSBzYXBwbHkoc3RyX3NwbGl0KHNhbXBsZSwgIl8iKSwgZnVuY3Rpb24oeCkgeFsyXSkpICU+JQogICAgICBncm91cF9ieShncm91cCkgJT4lCiAgICAgIG11dGF0ZShncl9tZWFuID0gbWVkaWFuKHZhbHVlKSkgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgYXJyYW5nZShncl9tZWFuKSAlPiUKICAgICAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAKICAKICByMl9kZiA8LSBnZXRfdmFyaWFuY2VfZXhwbGFpbmVkKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogICAgZmlsdGVyKGZhY3Rvcj09cGFzdGUwKCdGYWN0b3InLGYpKSAlPiUKICAgIG11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscyA9IGxldmVscyhmYWN0b3JfZGYkZ3JvdXApKSkKICAKICBwbDEgPC0gZmFjdG9yX2RmICU+JQogICAgICBnZ3Bsb3QoYWVzKGdyb3VwLCB2YWx1ZSkpICsKICAgICAgZ2VvbV9ib3hwbG90KCkgKwogICAgICBnZW9tX2ppdHRlcihhZXMoY29sb3I9IG9yZ2FuKSwgc2l6ZT0wLjcpICsKICAgICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MikgKwogICAgICBjb29yZF9mbGlwKCkgKwogICAgICB5bGFiKHBhc3RlMCgiRmFjdG9yICIsIGYpKSArCiAgICAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KQogIAogIHBsMiA8LSByMl9kZiAlPiUKICAgIGdncGxvdChhZXMoZ3JvdXAsIHZhbHVlKSkgKwogICAgZ2VvbV9jb2woKSArCiAgICBjb29yZF9mbGlwKCkgKwogICAgeWxhYigiJSB2YXJpYW5jZSBleHBsYWluZWQiKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgcmVtb3ZlX3lfYXhpcygpCiAgCiAgcGwxICsgcGwyICsgcGxvdF9sYXlvdXQod2lkdGhzPWMoMiwxKSwgZ3VpZGVzPSJjb2xsZWN0IikgCn0KCmdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3RvciA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYpewogIHIyX2RmIDwtIGdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lCiAgICBmaWx0ZXIoZmFjdG9yPT1wYXN0ZTAoJ0ZhY3RvcicsZikpIAogICAgIyBtdXRhdGUoZ3JvdXA9ZmFjdG9yKGdyb3VwLCBsZXZlbHMgPSApKQogIHRvcF9xdWFudF9yMiA8LSBxdWFudGlsZShyMl9kZiR2YWx1ZSwgcHJvYnMgPSBzZXEoMCwgMSwgYnkgPSAwLjIpKVsiODAlIl0KICB0b3BfZ3JvdXBzIDwtIHIyX2RmJGdyb3VwW3IyX2RmJHZhbHVlID49IHRvcF9xdWFudF9yMl0KICByZXR1cm4odG9wX2dyb3VwcykKfQoKc2F2ZV9mYWN0b3JfaWQgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBmaWdkaXIpewogICMjIE9yZGVyIGNlbGx0eXBlcyBieSBmYWN0b3IgdmFsdWVzCiAgcDEgPC0gcGxvdF9mYWN0b3Jfb3JkZXJlZChtb2ZhX3RyYWluZWQsIGYpCiAgCiAgIyMgUGxvdCBmYWN0b3IgdmFsdWVzIGFjcm9zcyBvcmdhbnMgZm9yIGNlbGx0eXBlcyB3aXRoIGhpZ2ggdmFyaWFuY2UgZXhwbGFpbmVkCiAgcDIgPC0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgZ3JvdXBzID0gZ2V0X3RvcF9jZWxsdHlwZV9wZXJfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZiksIGdyb3VwX2J5ID0gImdyb3VwIiwgCiAgICAgICAgICAgICAgY29sb3JfYnkgPSAib3JnYW4iLCAKICAgICAgICAgICAgICBkb3Rfc2l6ZSA9IDIsIGRvZGdlID0gVFJVRQogICAgICAgICAgICAgICkKICAKICAjIyBQbG90IGZhY3RvciB3ZWlnaHRzIG9uIGdlbmVzCiAgIyBwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IGYsIG5mZWF0dXJlcyA9IDUwLCB0ZXh0X3NpemUgPSAzLCBzaG93X2NvbG5hbWVzPUZBTFNFLAogICMgICAgICAgICAgICAgICAgICAgYW5ub3RhdGlvbl9zYW1wbGVzID0gYygib3JnYW4iLCAidGltZSIsICJtZXRob2QiLCAiZG9ub3IiKSkKICBwMyA8LSBwbG90X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgbmZlYXR1cmVzID0gMzAsIHRleHRfc2l6ZSA9IDMpICsKICAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQ9YygwLjEsIDAuMSkpCiAgCiAgZnVsbF9wbCA8LSAocDEgfCAocDIgLyBwMykpICsKICAgIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIpIAogIGdnc2F2ZShnbHVlKCJ7ZmlnZGlyfS9NT0ZBX3tzcGxpdH1fZmFjdG9ySURfZmFjdG9ye2Z9LnBkZiIpLCBwbG90PWZ1bGxfcGwsIHdpZHRoID0gMTUsIGhlaWdodCA9IDEwKQp9Cgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBwcmludChwYXN0ZTAoIlNhdmluZyBJRCBmb3IgRmFjdG9yICIsIGYsICIuLi4iKSkKICBzYXZlX2ZhY3Rvcl9pZChtb2ZhX3RyYWluZWQsIGY9ZiwgZmlnZGlyID0gZmlnZGlyKSAgCn0KCiMgc2F2ZV9mYWN0b3JfaWQobW9mYV90cmFpbmVkLCBmPTEsIGZpZ2RpciA9IGZpZ2RpcikgIAojIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSBmLCBuZmVhdHVyZXMgPSAzMCwgdGV4dF9zaXplID0gMykgKwojICAgIHNjYWxlX3lfZGlzY3JldGUoZXhwYW5kPWMoMC4xLCAwLjEpKQpgYGAKCgoKCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3RvciA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYpeyAtLT4KPCEtLSAgIHIyX2RmIDwtIGdldF92YXJpYW5jZV9leHBsYWluZWQobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUpW1sxXV0gJT4lIC0tPgo8IS0tICAgICBmaWx0ZXIoZmFjdG9yPT1wYXN0ZTAoJ0ZhY3RvcicsZikpICU+JSAtLT4KPCEtLSAgICAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzID0gbGV2ZWxzKGZhY3Rvcl9kZiRncm91cCkpKSAtLT4KPCEtLSAgIHRvcF9xdWFudF9yMiA8LSBxdWFudGlsZShyMl9kZiR2YWx1ZSwgcHJvYnMgPSBzZXEoMCwgMSwgYnkgPSAwLjIpKVsiODAlIl0gLS0+CjwhLS0gICB0b3BfZ3JvdXBzIDwtIHIyX2RmJGdyb3VwW3IyX2RmJHZhbHVlID49IHRvcF9xdWFudF9yMl0gLS0+CjwhLS0gICByZXR1cm4odG9wX2dyb3VwcykgLS0+CjwhLS0gfSAtLT4KCjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3I9MiwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGRvZGdlID0gVFJVRSwgYWRkX2JveHBsb3QgPSBUUlVFLCBjb2xvcl9ieT0iZG9ub3IiKSAtLT4KPCEtLSBwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGZhY3Rvcj0yLCBncm91cHM9Z2V0X3RvcF9jZWxsdHlwZV9wZXJfZmFjdG9yKG1vZmFfdHJhaW5lZCwgMilbMzo1XSwgZ3JvdXBfYnkgPSAib3JnYW4iLCBkb2RnZSA9IFRSVUUsIGFkZF9ib3hwbG90ID0gVFJVRSwgY29sb3JfYnk9Im9yZ2FuIikgKyAtLT4KPCEtLSAgIHlsaW0oMyw4KSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWdldF90b3BfY2VsbHR5cGVfcGVyX2ZhY3Rvcihtb2ZhX3RyYWluZWQsIDIpWzM6NV0sIGZhY3Rvcj0yLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPgo8IS0tICAgbGVmdF9qb2luKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhKSAlPiUgLS0+CjwhLS0gICBnZ3Bsb3QoYWVzKHZhbHVlLCBmaWxsPW9yZ2FuKSkgKyAtLT4KPCEtLSAgIGdlb21faGlzdG9ncmFtKCkgLS0+CjwhLS0gICAjIGdlb21fc21vb3RoKG1ldGhvZD0ibG0iKSArIC0tPgo8IS0tICAgZ2dwdWJyOjpzdGF0X2NvcigpIC0tPgo8IS0tIHBsb3RfZmFjdG9yc192c19jb3YobW9mYV90cmFpbmVkLCBncm91cHM9Z2V0X3RvcF9jZWxsdHlwZV9wZXJfZmFjdG9yKG1vZmFfdHJhaW5lZCwgMilbMzo1XSwgY292YXJpYXRlcyA9ICIiKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHcgPC0gZ2V0X3dlaWdodHMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gJ2FsbCcsIGFzLmRhdGEuZnJhbWUgPSBGQUxTRSkgLS0+CjwhLS0gYXMuZGF0YS5mcmFtZSh3JHNjYWxlZF9sb2djb3VudHMpICU+JSAtLT4KPCEtLSAgIHJvd25hbWVzX3RvX2NvbHVtbigiZ2VuZSIpICU+JSAtLT4KPCEtLSAgIHdyaXRlX2Nzdigifi9NT0ZBX3dlaWdodHMuY3N2IikgLS0+CjwhLS0gYGBgIC0tPgoKCiMjIyMgRXhwcmVzc2lvbiBvZiB0b3AgUjIgZmFjdG9ycwoKYGBge3J9CmdldF90b3Bfd2VpZ2h0X2dlbmVzIDwtIGZ1bmN0aW9uKG1vZmFfdHJhaW5lZCwgZiwgbl90b3A9MjAsIHdoaWNoPSJ0b3AiKXsKICB3X2RmIDwtIGdldF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUKICAgIGRwbHlyOjphcnJhbmdlKHZhbHVlKSAKICB0b3BfZ2VuZXMgPC0gd19kZiAlPiUKICAgICAgZHBseXI6OnRvcF9uKG5fdG9wLCB2YWx1ZSkgJT4lCiAgICAgIGRwbHlyOjpwdWxsKGZlYXR1cmUpICU+JQogICAgICBhcy5jaGFyYWN0ZXIoKQogIGJvdF9nZW5lcyA8LSAgd19kZiAlPiUKICAgICAgZHBseXI6OnRvcF9uKG5fdG9wLCAtdmFsdWUpICU+JQogICAgICBkcGx5cjo6cHVsbChmZWF0dXJlKSAlPiUKICAgICAgYXMuY2hhcmFjdGVyKCkKICBpZiAod2hpY2g9PSJ0b3AiKSB7CiAgICBnZW5lcyA8LSB0b3BfZ2VuZXMKICB9IGVsc2UgaWYgKHdoaWNoPT0iYm90dG9tIil7CiAgICBnZW5lcyA8LSBib3RfZ2VuZXMKICB9IGVsc2UgaWYgKHdoaWNoPT0iYm90aCIpewogICAgZ2VuZXMgPC0gYyh0b3BfZ2VuZXMsIGJvdF9nZW5lcykKICB9CiAgcmV0dXJuKGdlbmVzKQp9CgpwbG90X2RhdGFfdG9wX3dlaWdodHMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBjdCwgZiwgbl90b3A9MjAsIHdoaWNoPSJ0b3AiKXsKICBnZW5lcyA8LSBnZXRfdG9wX3dlaWdodF9nZW5lcyhtb2ZhX3RyYWluZWQsIGYsIHdoaWNoPXdoaWNoLCBuX3RvcD1uX3RvcCkKICBkYXRhIDwtIGdldF9kYXRhKG1vZmFfdHJhaW5lZCwgZ3JvdXBzPWN0KVtbMV1dW1sxXV1bZ2VuZXMsXQogIAogIHBsX2RmIDwtIHJlc2hhcGUyOjptZWx0KGRhdGEsIHZhcm5hbWVzPWMoImdlbmUiLCAic2FtcGxlIikpICU+JQogICAgZHBseXI6OmxlZnRfam9pbihzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkpICU+JQogICAgZHBseXI6OmFycmFuZ2UoYWdlKSAlPiUKICAgIGRwbHlyOjptdXRhdGUoc2FtcGxlPWZhY3RvcihzYW1wbGUsIGxldmVscz11bmlxdWUoc2FtcGxlKSkpICU+JQogICAgZHBseXI6Omdyb3VwX2J5KGdlbmUpICU+JQogICAgZHBseXI6Om11dGF0ZSh2YWx1ZT1zY2FsZSh2YWx1ZSkpCiAgcGxfZGYgJT4lCiAgICBnZ3Bsb3QoYWVzKHNhbXBsZSwgZ2VuZSwgZmlsbD12YWx1ZSkpICsKICAgIGdlb21fdGlsZSgpICsKICAgIGZhY2V0X2dyaWQoLn5vcmdhbiwgc3BhY2U9ImZyZWUiLCBzY2FsZXM9ImZyZWUiKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50MihoaWdoPSJyZWQiLCBsb3c9ImJsdWUiLCBuYW1lPSJTY2FsZWRcbmV4cHJlc3Npb24iKSArCiAgICB4bGFiKCItLS0tYWdlLS0tPiIpICsgeWxhYihnbHVlKCJ7d2hpY2h9IHdlaWdodCBnZW5lcyIpKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemU9MTYpICsKICAgIHRoZW1lKGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIGdndGl0bGUoZ2x1ZSgne2N0fSAtIHtmfScpKQp9Cgpmb3IgKGcgaW4gYWxsX2dyb3Vwcyl7CiAgZnMgPC0gZ2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlKG1vZmFfdHJhaW5lZCwgZywgbWluX1IyPTIpCiAgZnMgPC0gZnNbIWZzICVpbiUgZXhjbHVkZV9mYWN0b3JzXQogIGlmIChsZW5ndGgoZnMpID4gMCl7CiAgICB0b3BfcGxvdHMgPC0gbGFwcGx5KGZzLCBmdW5jdGlvbih4KSAocGxvdF9kYXRhX3RvcF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZywgeCwgd2hpY2g9InRvcCIpICsgcmVtb3ZlX3hfYXhpcygpKSAvICAKICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90X2RhdGFfdG9wX3dlaWdodHMobW9mYV90cmFpbmVkLCBnLCB4LCB3aGljaD0iYm90dG9tIikgKyBnZ3RpdGxlKCIiKQogICAgKQogICAgZnVsbF9wbCA8LXdyYXBfcGxvdHModG9wX3Bsb3RzLCBuY29sPTEpIAogICAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3RvcF9mYWN0b3JzX2V4cHJfe2d9LnBkZiIpLHBsb3Q9ZnVsbF9wbCwgIHdpZHRoPTEyLCBoZWlnaHQgPSAxMCpsZW5ndGgodG9wX3Bsb3RzKSkKICAgIH0gIAp9CmBgYAoKCjwhLS0gYGBge3J9IC0tPgo8IS0tICMjIHJlb3JkZXIgb3JnYW5zIC0tPgo8IS0tIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSRvcmdhbiA8LSBmYWN0b3Ioc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpJG9yZ2FuLCBsZXZlbHM9b3JnYW5fb3JkZXIpIC0tPgo8IS0tIGZvciAoZyBpbiBhbGxfZ3JvdXBzKXsgLS0+CjwhLS0gICBmcyA8LSBnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCBnLCBtaW5fUjI9MikgLS0+CjwhLS0gICBmcyA8LSBmc1shZnMgJWluJSBleGNsdWRlX2ZhY3RvcnNdIC0tPgo8IS0tICAgaWYgKGxlbmd0aChmcykgPiAwKXsgLS0+CjwhLS0gICAgICAgcGwgPC0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLGdyb3VwcyA9IGMoZyksIGNvbG9yX2J5PSJvcmdhbiIsIGRvdF9zaXplID0gMywgZmFjdG9ycyA9IGZzLCAtLT4KPCEtLSAgICAgICAgICAgICBhZGRfYm94cGxvdCA9IFRSVUUsIGJveHBsb3RfYWxwaGEgPSAwLjEsIC0tPgo8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gIm9yZ2FuIiwgc2NhbGUgPSApICsgLS0+CjwhLS0gICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpICsgLS0+CjwhLS0gICAgIGdndGl0bGUoZykgLS0+CjwhLS0gICAgIHByaW50KHBsKSAtLT4KPCEtLSAgIH0gLS0+CjwhLS0gICB9IC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0xNX0gLS0+CjwhLS0gZ3JfdG9wX2ZhY3RvcnMgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpIGRhdGEuZnJhbWUoZ3JvdXA9ZywgdG9wX2ZhY3RvcnM9Z2V0X3RvcF9mYWN0b3JfcGVyX2NlbGx0eXBlKG1vZmFfdHJhaW5lZCwgZywgbWluX1IyPTIpKSkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShkcGx5cjo6YmluZF9yb3dzKSAlPiUgLS0+CjwhLS0gICBkcGx5cjo6ZmlsdGVyKCF0b3BfZmFjdG9ycyAlaW4lIGV4Y2x1ZGVfZmFjdG9ycykgLS0+Cgo8IS0tIGZvciAoZiBpbiB1bmlxdWUoZ3JfdG9wX2ZhY3RvcnMkdG9wX2ZhY3RvcnMpKXsgLS0+CjwhLS0gICBncyA8LSBkcGx5cjo6ZmlsdGVyKGdyX3RvcF9mYWN0b3JzLCB0b3BfZmFjdG9ycz09ZikgJT4lIGRwbHlyOjpwdWxsKGdyb3VwKSAtLT4KPCEtLSAgIHBsX2xzIDwtIGxhcHBseShncywgZnVuY3Rpb24oZykgcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLGdyb3VwcyA9IGMoZyksIGNvbG9yX2J5PSJvcmdhbiIsIGRvdF9zaXplID0gMywgZmFjdG9ycyA9IGYsIC0tPgo8IS0tICAgICAgICAgICAgIGFkZF9ib3hwbG90ID0gVFJVRSwgYm94cGxvdF9hbHBoYSA9IDAuMSwgIC0tPgo8IS0tICAgICAgICAgICAgIGdyb3VwX2J5ID0gJ2dyb3VwJywgZG9kZ2UgPSBUUlVFKSArIC0tPgo8IS0tICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgKyAtLT4KPCEtLSAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArIC0tPgo8IS0tICAgICBnZ3RpdGxlKGcpKSAtLT4KPCEtLSAgcHJpbnQod3JhcF9wbG90cyhwbF9scykgKyBwbG90X2xheW91dChndWlkZXM9ImNvbGxlY3QiKSArIHBsb3RfYW5ub3RhdGlvbih0aXRsZT1mKSkgIC0tPgo8IS0tICAgfSAtLT4KPCEtLSBgYGAgLS0+CgpgYGB7ciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NX0KZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA1LCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICBkcGx5cjo6ZmlsdGVyKGZhY3Rvcj09IkZhY3RvcjUiKSAlPiUKICBkcGx5cjo6YXJyYW5nZSh2YWx1ZSkgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgJT4lCiAgIyBkcGx5cjo6ZmlsdGVyKHZhbHVlID4gMSkgJT4lCiAgZ2dwbG90KGFlcyhncm91cCwgdmFsdWUpKSArCiAgZ2VvbV9jb2woKSArCiAgY29vcmRfZmxpcCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAyLCBjb2xvcj0icmVkIiwgbGluZXR5cGU9MikgKwogIHlsYWIoIiUgVmFyLiBleHBsYWluZWQiKQoKZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA1LCBhcy5kYXRhLmZyYW1lID0gVFJVRSlbWzFdXSAlPiUKICBkcGx5cjo6ZmlsdGVyKGdyb3VwPT0iQ0QxNitfTUFDUk9QSEFHRSIpICU+JQogIGRwbHlyOjpmaWx0ZXIoIWZhY3RvciAlaW4lIGV4Y2x1ZGVfZmFjdG9ycykgJT4lCiAgZHBseXI6OmFycmFuZ2UodmFsdWUpICU+JQogIGRwbHlyOjptdXRhdGUoZmFjdG9yPWZhY3RvcihmYWN0b3IsIGxldmVscz11bmlxdWUoZmFjdG9yKSkpICU+JQogICMgZHBseXI6OmZpbHRlcih2YWx1ZSA+IDEpICU+JQogIGdncGxvdChhZXMoZmFjdG9yLCB2YWx1ZSkpICsKICBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDIsIGNvbG9yPSJyZWQiLCBsaW5ldHlwZT0yKSArCiAgeWxhYigiJSBWYXIuIGV4cGxhaW5lZCIpCgoKIyAKIyBncyA8LSBkcGx5cjo6ZmlsdGVyKGdyX3RvcF9mYWN0b3JzLCB0b3BfZmFjdG9ycz09IkZhY3RvcjQiKSAlPiUgZHBseXI6OnB1bGwoZ3JvdXApCiMgZ3JvdXBzID0gZ3NbIWdzICVpbiUgYygiRU9fQkFTT19NQVNUIiwgIlBSRV9QUk9fQiIsICJEQzIiKV0KIyAKIyBwbG90X2RhdGFfaGVhdG1hcChtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDQsIGdyb3VwcyA9IGMoIk5FVVRST1BISUwiKSwgc2hvd19jb2xuYW1lcz1GQUxTRSwgYW5ub3RhdGlvbl9zYW1wbGVzID0gJ29yZ2FuJywgYW5ub3RhdGlvbl9jb2xvcnM9bGlzdChvcmdhbj1vcmdfY29sb3JzKSwgZmVhdHVyZXMgPSA1MCkKYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xOCwgZmlnLmhlaWdodD03fQptaW5tYXhfbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgsIG5hLnJtID0gVFJVRSkgewogICAgcmV0dXJuKCh4LSBtaW4oeCkpIC8obWF4KHgpLW1pbih4KSkpCn0KCnBsb3RfZGF0YV90b3Bfd2VpZ2h0c19jbHVzdGVyZWQgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBjdHMsIGYsIG5fdG9wPTIwLCB3aGljaD0idG9wIiwgc2NhbGVfZGF0YT1UUlVFKXsKICBnZW5lcyA8LSBnZXRfdG9wX3dlaWdodF9nZW5lcyhtb2ZhX3RyYWluZWQsIGYsIHdoaWNoPXdoaWNoLCBuX3RvcD1uX3RvcCkKICAKICBnZW5lc19hbm5vIDwtIGRhdGEuZnJhbWUoZ2VuZT1nZW5lcykgCiAgaWYgKHdoaWNoIT0iYm90aCIpeyBnZW5lc19hbm5vW1sid2VpZ2h0Il1dIDwtIHJlcCh3aGljaCwgbl90b3ApIH0gIGVsc2UgeyBnZW5lc19hbm5vW1sid2VpZ2h0Il1dIDwtIGMocmVwKCJ0b3AiLCBuX3RvcCksIHJlcCgiYm90dG9tIiwgbl90b3ApKSB9CiAgCiAgZGF0YV9scyA8LSBnZXRfZGF0YShtb2ZhX3RyYWluZWQsIGdyb3Vwcz1jdHMpW1sxXV0KICBkYXRhIDwtIFJlZHVjZShjYmluZCwgZGF0YV9scylbZ2VuZXMsXQogIAogIGN0X3BsX2xzIDwtIGxhcHBseShjdHMsIGZ1bmN0aW9uKGN0KXsKICAgIGN0X3NhbXBsZXMgPC0gY29sbmFtZXMobW9mYV90cmFpbmVkQGRhdGFbWzFdXVtbY3RdXSkKICAgIGN0X2RhdGEgPC0gZGF0YVssY3Rfc2FtcGxlc10KICAgIGlmIChzY2FsZV9kYXRhKXsKICAgICAgY3RfZGF0YSA8LSB0KGFwcGx5KGN0X2RhdGEsIDEsIG1pbm1heF9ub3JtYWxpemUpKQogICAgfQogICAgY2xfaGVhdG1hcCA8LSBwaGVhdG1hcDo6cGhlYXRtYXAoY3RfZGF0YSwgc2hvd19jb2xuYW1lcz0gRkFMU0UsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCApCiAgICBjb2xfb3JkZXIgPC0gY2xfaGVhdG1hcCR0cmVlX2NvbCRsYWJlbHNbY2xfaGVhdG1hcCR0cmVlX2NvbCRvcmRlcl0KICAgIAogICAgcGxfZGYgPC0gcmVzaGFwZTI6Om1lbHQoY3RfZGF0YSwgdmFybmFtZXM9YygiZ2VuZSIsICJzYW1wbGUiKSkgJT4lCiAgICAgICAgZHBseXI6OmxlZnRfam9pbihzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkpICU+JQogICAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oZ2VuZXNfYW5ubykgJT4lCiAgICAgICAgZHBseXI6Om11dGF0ZShzYW1wbGU9ZmFjdG9yKHNhbXBsZSwgbGV2ZWxzPWNvbF9vcmRlciksCiAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHQ9ZmFjdG9yKHdlaWdodCwgbGV2ZWxzPWMoInRvcCIsICJib3R0b20iKSkpIAogICAgCiAgICBwbF9iYXIgPC0gcGxfZGYgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHNhbXBsZSwgIm9yZ2FuIiwgZmlsbD1vcmdhbikpICsKICAgICAgZ2VvbV90aWxlKCkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgKwogICAgICB0aGVtZV92b2lkKCkgKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCiAgICBwbF9obSA8LSBwbF9kZiAlPiUKICAgICAgZ2dwbG90KGFlcyhzYW1wbGUsIGdlbmUsIGZpbGw9dmFsdWUpKSArCiAgICAgICAgZ2VvbV90aWxlKCkgKwogICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbj0ibWFnbWEiLCBuYW1lPSJTY2FsZWRcbmV4cHJlc3Npb24iKSArCiAgICAgICAgeGxhYihncm91cF9sYWJlbGxlcltjdF0pICsKICAgICAgICBmYWNldF9ncmlkKHdlaWdodH4uLCBzY2FsZXM9ImZyZWUiLCBzcGFjZT0iZnJlZSIpICsKICAgICAgICB0aGVtZV9idyhiYXNlX3NpemU9MTIpICsKICAgICAgICB0aGVtZShheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQogICAgKHBsX2JhciAvIHBsX2htKSArIHBsb3RfbGF5b3V0KGhlaWdodHMgPSBjKDEsMTApKQogICAgfSkKICBpZiAobGVuZ3RoKGN0X3BsX2xzKSA+IDEpewogICAgIyMgUmVtb3ZlIGdlbmUgbmFtZXMgdG8gYWxsIGV4Y2VwdCAxc3QgcGxvdAogICAgY3RfcGxfbHNbMjpsZW5ndGgoY3RfcGxfbHMpXSA8LSBsYXBwbHkoY3RfcGxfbHNbMjpsZW5ndGgoY3RfcGxfbHMpXSwgZnVuY3Rpb24ocCkgcCArIHJlbW92ZV95X2F4aXMoKSkKICAgIAogICAgIyMgUmVtb3ZlIHN0cmlwIG5hbWVzIHRvIGFsbCBleGNlcHQgbGFzdCBwbG90CiAgICBjdF9wbF9sc1sxOihsZW5ndGgoY3RfcGxfbHMpLTEpXSA8LSBsYXBwbHkoY3RfcGxfbHNbMToobGVuZ3RoKGN0X3BsX2xzKS0xKV0sIGZ1bmN0aW9uKHApIHAgKyB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLHN0cmlwLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkpCiAgICB3cmFwX3Bsb3RzKGN0X3BsX2xzKSArIAogICAgcGxvdF9sYXlvdXQoZ3VpZGVzPSJjb2xsZWN0IiwgbnJvdyA9IDEpCiAgfSBlbHNlIHsKICAgIGN0X3BsX2xzW1sxXV0KICB9Cn0KCnBsb3RfZmFjdG9yX29yZ2FuX2JveHBsb3RzIDwtIGZ1bmN0aW9uKGYsIGN0cyl7CiAgcGxfbHMgPC0gbGFwcGx5KGN0cywgZnVuY3Rpb24oZykgcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLGdyb3VwcyA9IGMoZyksIGNvbG9yX2J5PSJvcmdhbiIsIGRvdF9zaXplID0gMywgZmFjdG9ycyA9IGYsCiAgICAgICAgICAgIGFkZF9ib3hwbG90ID0gVFJVRSwgYm94cGxvdF9hbHBoYSA9IDAuMSwgCiAgICAgICAgICAgIGdyb3VwX2J5ID0gJ2dyb3VwJywgZG9kZ2UgPSBUUlVFKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9b3JnX2NvbG9ycykgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSArCiAgICBnZ3RpdGxlKGdyb3VwX2xhYmVsbGVyW2ddKSAKICAgICkKIHdyYXBfcGxvdHMocGxfbHMpICsgCiAgIHBsb3RfbGF5b3V0KGd1aWRlcz0iY29sbGVjdCIsIG5yb3c9MSkgKyAKICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPWYpIAp9CgpoaWdoX3IyX2dyb3Vwc19kZl9maWx0IDwtIGhpZ2hfcjJfZ3JvdXBzX2RmICU+JQogIGRwbHlyOjpmaWx0ZXIob3JnX0FNSSA+IDAuMjUpICU+JQogIGRwbHlyOjphcnJhbmdlKC0gb3JnX0FNSSkgCgpmb3IgKGZhY3QgaW4gYXMuY2hhcmFjdGVyKHVuaXF1ZShoaWdoX3IyX2dyb3Vwc19kZl9maWx0JGZhY3RvcikpKXsKICBmYWN0X2N0cyA9IGFzLmNoYXJhY3RlcihoaWdoX3IyX2dyb3Vwc19kZl9maWx0JGdyb3VwW2hpZ2hfcjJfZ3JvdXBzX2RmX2ZpbHQkZmFjdG9yPT1mYWN0XSkKICBwX3RvcCA8LSBwbG90X2ZhY3Rvcl9vcmdhbl9ib3hwbG90cyhjdHM9ZmFjdF9jdHMsIGY9ZmFjdCkKICBwX2JvdHRvbSA8LSBwbG90X2RhdGFfdG9wX3dlaWdodHNfY2x1c3RlcmVkKG1vZmFfdHJhaW5lZCwgY3RzPWZhY3RfY3RzLCBmPWZhY3QsIHdoaWNoID0gImJvdGgiLCBzY2FsZV9kYXRhID0gVFJVRSkKICBmX3BsIDwtIChwX3RvcCAvIHBfYm90dG9tKSArCiAgICBwbG90X2xheW91dChoZWlnaHRzID0gYygxLDIuNSkpCiAgCiAgZ2dzYXZlKGdsdWUoIntmaWdkaXJ9L3tmYWN0fV90b3Bfb3JnYW5fQU1JX3Bsb3QucGRmIiksIHBsb3Q9Zl9wbCwgIHdpZHRoPTUgKyAoMypsZW5ndGgoZmFjdF9jdHMpKSwgaGVpZ2h0ID0gOSkKfQpgYGAKCgoKIyMjIEdTRUEKYGBge3J9CiMgQmlvY01hbmFnZXI6Omluc3RhbGwoIk1PRkFkYXRhIikKbGlicmFyeShNT0ZBZGF0YSkKdXRpbHM6OmRhdGEocmVhY3RvbWVHUykKaGVhZChyb3duYW1lcyhyZWFjdG9tZUdTKSkKCiMjIFJlbW92ZSByb3cgd2l0aCBOQQpyZWFjdG9tZUdTIDwtIHJlYWN0b21lR1NbIWlzLm5hKHJvd25hbWVzKHJlYWN0b21lR1MpKSxdCmBgYAoKYGBge3J9CkJpb2NNYW5hZ2VyOjppbnN0YWxsKCdlbnNlbWJsZGInKQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikKaGcucGFpcnMgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXhkYXRhIiwgImh1bWFuX2N5Y2xlX21hcmtlcnMucmRzIiwgcGFja2FnZT0ic2NyYW4iKSkKYWxsX2dlbmVzIDwtIGVuc2VtYmxkYjo6Z2VuZXMoRW5zRGIuSHNhcGllbnMudjg2KQpkZXRhY2gocGFja2FnZTpFbnNEYi5Ic2FwaWVucy52ODYpCmRldGFjaChwYWNrYWdlOmVuc2VtYmxkYikKCiMgZ2VuZV9uYW1lXzJfaWQgPC0gZnVuY3Rpb24oZ2VuZSl7CiMgICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkKIyB9CiMgCiMgZ2VuZV9pZHMgPC0gc2FwcGx5KG1vZmFfdHJhaW5lZEBmZWF0dXJlc19tZXRhZGF0YSRmZWF0dXJlLCBnZW5lX25hbWVfMl9pZCkKIyByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcwojIHJvd0RhdGEoc2NlKVsiZ2VuZV9uYW1lIl0gPC0gcm93bmFtZXMoc2NlKQoKZ2VuZV9uYW1lc19yZWFjdG9tZSA8LSBhbGxfZ2VuZXNbY29sbmFtZXMocmVhY3RvbWVHUyldJGdlbmVfbmFtZQpjb2xuYW1lcyhyZWFjdG9tZUdTKSA8LSBnZW5lX25hbWVzX3JlYWN0b21lCmBgYAoKU3Vic2V0IHRvIGdlbmVzIHRlc3RlZApgYGB7cn0KcmVhY3RvbWVHU191bml2ZXJzZSA8LSByZWFjdG9tZUdTWywgY29sbmFtZXMocmVhY3RvbWVHUykgJWluJSBtb2ZhX3RyYWluZWRAZmVhdHVyZXNfbWV0YWRhdGEkZmVhdHVyZV0KYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD03fQojIEdTRUEgb24gcG9zaXRpdmUgd2VpZ2h0cywgd2l0aCBkZWZhdWx0IG9wdGlvbnMKcmVzLnBvc2l0aXZlIDwtIHJ1bl9lbnJpY2htZW50KG1vZmFfdHJhaW5lZCwKICB2aWV3PSdjb3JyZWN0ZWRfbG9nY291bnRzJywKICAjIHN0YXRpc3RpY2FsLnRlc3QgPSAnY29yLmFkai5wYXJhbWV0cmljJywKICBmZWF0dXJlLnNldHMgPSByZWFjdG9tZUdTX3VuaXZlcnNlLCAKICBzaWduID0gInBvc2l0aXZlIiwKKQoKIyBHU0VBIG9uIG5lZ2F0aXZlIHdlaWdodHMsIHdpdGggZGVmYXVsdCBvcHRpb25zCnJlcy5uZWdhdGl2ZSA8LSBydW5fZW5yaWNobWVudChtb2ZhX3RyYWluZWQsIAogIHZpZXc9J3NjYWxlZF9sb2djb3VudHMnLAogICMgc3RhdGlzdGljYWwudGVzdCA9ICdjb3IuYWRqLnBhcmFtZXRyaWMnLAogIGZlYXR1cmUuc2V0cyA9IHJlYWN0b21lR1NfdW5pdmVyc2UsIAogIHNpZ24gPSAibmVnYXRpdmUiCikKCgpmb3IgKGYgaW4gMTptb2ZhX3RyYWluZWRAZGltZW5zaW9ucyRLKXsKICBpZiAobWluKHJlcy5wb3NpdGl2ZSRwdmFsLmFkalsscGFzdGUwKCJGYWN0b3IiLCBmKV0pIDwgMC4xKSB7CiAgICBwcmludChwbG90X2VucmljaG1lbnQocmVzLnBvc2l0aXZlLCBmYWN0b3IgPSBmLCBhbHBoYT0wLjEpICsgZ2d0aXRsZSgiUG9zaXRpdmUgd2VpZ2h0cyIpICsKICAgICAgICAgICAgcGxvdF9lbnJpY2htZW50KHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gZiwgYWxwaGE9MC4xKSArIGdndGl0bGUoIk5lZ2F0aXZlIHdlaWdodHMiKSArCiAgICAgICAgICAgICAgcGxvdF9hbm5vdGF0aW9uKHRpdGxlPXBhc3RlMCgiRmFjdG9yIiwgZikpKQogICAgICB9CiAgfQpgYGAKCmBgYHtyfQpzaWduaWZfcGF0aHdheXMgPC0gcm93bmFtZXMoZGF0YS5mcmFtZShyZXMubmVnYXRpdmUkcHZhbC5hZGopKVtvcmRlcihkYXRhLmZyYW1lKHJlcy5uZWdhdGl2ZSRwdmFsLmFkailbWyJGYWN0b3I4Il1dKVswOjEwXV0KY29sbmFtZXMocmVhY3RvbWVHU191bml2ZXJzZSlbcmVhY3RvbWVHU191bml2ZXJzZVtzaWduaWZfcGF0aHdheXNbNV0sXT09MV0KcGxvdF9lbnJpY2htZW50X2RldGFpbGVkKHJlcy5uZWdhdGl2ZSwgZmFjdG9yID0gOCkKYGBgCgotLS0KCiMjIE5vdGVzCgotIEZhY3RvcjIgc2VwYXJhdGVzIEJNIGZyb20gcmVzdAotIEZhY3RvcjU6IGltbWF0dXJlIFZTIG1hdHVyZSBCIGNlbGwgcGhlbm90eXBlLCBzZXBhcmF0ZXMgbWF0dXJlIEIgY2VsbHMgYW5kIEIxIGNlbGxzIGluIGxpdmVyIGFuZCBCTSBmcm9tIHRoZSBvdGhlcnMsIG1vcmUgbWF0dXJlIHBoZW5vdHlwZSAobG93ZXIgZXhwciBvZiBWUFJFQjEgYW5kIGNvLikKCi0tLQojIyMjIEtOTiBncmFwaCBwZXIgY2VsbHR5cGUKCmBgYHtyfQojIyBHZXQgZmFjdG9ycyB0aGF0IGV4cGxhaW4gbW9zdCB2YXJpYW5jZSBpbiBlYWNoIGNlbGx0eXBlCmdldF90b3BfZmFjdG9yX3Blcl9jZWxsdHlwZSA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGdyLCBtaW5fUjI9Mil7CiAgZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JQogICAgZHBseXI6OmZpbHRlcihncm91cD09Z3IpICU+JQogICAgZHBseXI6OmZpbHRlcih2YWx1ZSA+PSBtaW5fUjIpICU+JQogICAgZHBseXI6OnB1bGwoZmFjdG9yKSAlPiUKICAgIGFzLmNoYXJhY3RlcigpCn0KCiMjIE1ha2UgS05OIGdyYXBoIGJhc2VkIG9uIHNpbWlsYXJpdHkgb2YgdG9wIGZhY3RvcnMgZm9yIGVhY2ggY2VsbHR5cGUKZ2V0X2N0X0tOTl9ncmFwaCA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGdyLCBtaW5fUjI9NSwgaz01KXsKICAjIyBHZXQgZmFjdG9ycyB0aGF0IGV4cGxhaW4gbW9zdCB2YXJpYW5jZSBwZXIgY2VsbHR5cGUKICBmcyA8LSBnZXRfdG9wX2ZhY3Rvcl9wZXJfY2VsbHR5cGUobW9mYV90cmFpbmVkLCBnciwgbWluX1IyID0gbWluX1IyKQogIAogICMjIEV4Y2x1ZGUgdGVjaG5pY2FsIGZhY3RvcnMKICBmcyA8LSBmc1shZnMgJWluJSBleGNsdWRlX2ZhY3RvcnNdCiAgCiAgIyMgTWFrZSBLTk4gZ3JhcGggZnJvbSB0b3AgZmFjdG9ycwogIFogPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBncm91cHM9Z3IsIGZhY3RvcnMgPSBmcylbWzFdXQogIGtubl9jdCA8LSBidWlsZEtOTkdyYXBoKHQoWiksIGs9aykKICAKICAjIyBBZGQgYXR0cmlidXRlcwogIG1ldGFkYXRhX2N0IDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKVtyb3duYW1lcyhaKSxdCiAgIyBjb3ZhcmlhdGVzCiAgVihrbm5fY3QpJG9yZ2FuIDwtIG1ldGFkYXRhX2N0JG9yZ2FuCiAgVihrbm5fY3QpJGFnZSA8LSBtZXRhZGF0YV9jdCRhZ2UKICBWKGtubl9jdCkkbl9jZWxscyA8LSBtZXRhZGF0YV9jdCRuX2NlbGxzCiAgVihrbm5fY3QpJG1ldGhvZCA8LSBtZXRhZGF0YV9jdCRtZXRob2QKICBWKGtubl9jdCkkZG9ub3IgPC0gbWV0YWRhdGFfY3QkZG9ub3IKICAjIHRvcCBmYWN0b3JzCiAgZm9yIChjIGluIGNvbG5hbWVzKFopKXsKICAgdmVydGV4X2F0dHIoa25uX2N0KVtbY11dIDwtIFpbLGNdICAKICB9CiAgCiAgcmV0dXJuKGtubl9jdCkKICB9CgojIyBQbG90IEtOTiBncmFwaApwbG90X2N0X0tOTl9ncmFwaCA8LSBmdW5jdGlvbihrbm4sIGNvbG9yX2J5PSJvcmdhbiIpewogICMjIERlZmluZSBjb2xvciAKICBpZiAoIWNvbG9yX2J5ICVpbiUgbmFtZXModmVydGV4X2F0dHIoa25uKSkpewogICAgc3RvcCgic3BlY2lmaWVkIGNvbG9yX2J5IHZhcmlhYmxlIGlzIG5vdCBpbiB2ZXJ0ZXhfYXR0cihrbm4pIikKICB9CiAgCiAgaWYgKGNvbG9yX2J5PT0ib3JnYW4iKXsgCiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPW9yZ19jb2xvcnMpCiAgfSBlbHNlIGlmIChpcy5udW1lcmljKHZlcnRleF9hdHRyKGtubiwgY29sb3JfYnkpKSl7CiAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl92aXJpZGlzX2Mob3B0aW9uPSJtYWdtYSIpICAKICB9IGVsc2UgewogICAgICBzY2FsZV9jb2xvcl9rbm5ncmFwaCA8LSBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpCiAgICB9CiAgCiAgdmVydGV4X2F0dHIoa25uLCAiY29sb3JfYnkiKSA8LSB2ZXJ0ZXhfYXR0cihrbm4sIGNvbG9yX2J5KQogIAogIGdncmFwaChrbm4pICsKICAgIGdlb21fZWRnZV9saW5rMCgpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3I9Y29sb3JfYnksIHNpemU9bl9jZWxscykpICsKICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHNjYWxlX2NvbG9yX2tubmdyYXBoICsKICAgIHNjYWxlX3NpemUocmFuZ2U9YygyLDcpKSAKICB9Cgprbm5fZ3JhcGhfcGwgPC0gbGFwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpewogIGtubiA8LSBnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgZywgaz01LCBtaW5fUjIgPSAyKQogIHBsb3RfY3RfS05OX2dyYXBoKGtubiwgY29sb3JfYnkgPSAnb3JnYW4nKSArIGdndGl0bGUoZykKICB9KQoKa25uX2dyYXBoX3BsCmBgYAoKYGBge3J9CiMjIFNjb3JlIGNvbm5lY3Rpdml0eSBiZXR3ZWVuIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBvcmdhbgouY2FsY19jb25uZWN0aXZpdHlfc2NvcmUgPC0gZnVuY3Rpb24oa25uLCBvKXsKICBhZGogPC0gZ2V0LmFkamFjZW5jeShrbm4pCiAgbl9vcmcgPC0gc3VtKFYoa25uKSRvcmdhbj09bykKICBuX290aGVyIDwtIHN1bShWKGtubikkb3JnYW4hPW8pCiAgd2l0aGluX2VkZ2VzIDwtIHN1bShhZGpbVihrbm4pJG9yZ2FuPT1vLFYoa25uKSRvcmdhbj09b10pCiAgYmV0d2Vlbl9lZGdlcyA8LSBzdW0oYWRqW1Yoa25uKSRvcmdhbj09byxWKGtubikkb3JnYW4hPW9dKQogIHNjb3JlIDwtICh3aXRoaW5fZWRnZXMvYmV0d2Vlbl9lZGdlcykqKG5fb3RoZXIvbl9vcmcpCiAgcmV0dXJuKHNjb3JlKQogIH0KCiMjIENhbGN1bGF0ZSBjb25uZWN0aXZpdHkgc2NvcmUgZm9yIHBlcm11dGF0aW9ucyBvZiBub2RlIGxhYmVscwpjb25uX3Njb3JlX3Rlc3QgPC0gZnVuY3Rpb24oa25uLCBvLCBuX3Blcm09MTAwMCl7CiAgcmVhbF9zY29yZSA8LSAuY2FsY19jb25uZWN0aXZpdHlfc2NvcmUoa25uLCBvKQogICMjIFJhbmRvbSBwZXJtdXRhdGlvbnMKICByYW5kX3Njb3JlcyA8LSBjKCkKICBmb3IgKGkgaW4gMTpuX3Blcm0pewogICAgcmFuZF9rbm4gPC0ga25uCiAgICBWKHJhbmRfa25uKSRvcmdhbiA8LSBzYW1wbGUoVihrbm4pJG9yZ2FuKQogICAgcmFuZF9zY29yZXMgPC0gYyhyYW5kX3Njb3JlcywgLmNhbGNfY29ubmVjdGl2aXR5X3Njb3JlKHJhbmRfa25uLCBvKSkgICAKICB9CiAgCiAgcF92YWwgPC0gc3VtKGMocmFuZF9zY29yZXMsIHJlYWxfc2NvcmUpID49IHJlYWxfc2NvcmUpLyhuX3Blcm0gKyAxKQogIGlmIChwX3ZhbCA8IDJlLTE2KXsgcF92YWwgPC0gMmUtMTZ9CiAgcmV0dXJuKGMoJ3Njb3JlJz1yZWFsX3Njb3JlLCdwX3ZhbHVlJz1wX3ZhbCkpCn0KCiMjIENhbGN1bGF0ZSBjb25uZWN0aXZpdHkgc2NvcmUgKyBzaWduaWZpY2FuY2Ugd2l0aCBwZXJtdXRhdGlvbiB0ZXN0CnRlc3RfY29ubl9ncm91cCA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGcsIGs9NSwgbWluX1IyID0gMiwgbl9wZXJtPTEwMDApewogIGtubiA8LSBnZXRfY3RfS05OX2dyYXBoKG1vZmFfdHJhaW5lZCwgZywgaz1rLCBtaW5fUjIgPSBtaW5fUjIpCiAgdGVzdF9vcmdzIDwtIG5hbWVzKHRhYmxlKFYoa25uKSRvcmdhbikpW3RhYmxlKFYoa25uKSRvcmdhbikgPiAyXQogIHJldHVybihzYXBwbHkodGVzdF9vcmdzLCBmdW5jdGlvbihvKSBjb25uX3Njb3JlX3Rlc3Qoa25uLCBvLCBuX3Blcm09bl9wZXJtKSkpCiAgfQoKIyMgVGVzdCBpbiB3aWRlc3ByZWFkIGNlbGwgdHlwZXMKY29ubmVjdGl2aXR5X3Rlc3RfbHMgPC0gbGFwcGx5KGFubm9fb3JkZXJbMToxMF0sIGZ1bmN0aW9uKGcpIHRlc3RfY29ubl9ncm91cChtb2ZhX3RyYWluZWQsIGcpKQpjb25uZWN0aXZpdHlfdGVzdF9scyA8LSBzZXROYW1lcyhjb25uZWN0aXZpdHlfdGVzdF9scywgYW5ub19vcmRlclsxOjEwXSkKCmNvbm5lY3Rpdml0eV90ZXN0X2RmIDwtIGltYXAoY29ubmVjdGl2aXR5X3Rlc3RfbHMsIH4gZGF0YS5mcmFtZSh0KC54KSkgJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJvcmdhbiIpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6Om11dGF0ZShncm91cD0ueSkpICU+JQogIHB1cnJyOjpyZWR1Y2UoZHBseXI6OmJpbmRfcm93cykgJT4lCiAgZHBseXI6Om11dGF0ZShpc19zaWduaWYgPSBpZmVsc2UocF92YWx1ZSA8IDAuMDEsIFRSVUUsIEZBTFNFKSkgCgpjb25uZWN0aXZpdHlfdGVzdF9kZiAlPiUKICBnZ3Bsb3QoYWVzKG9yZ2FuLCBncm91cCxmaWxsPWxvZzEwKHNjb3JlKSkpICsKICBnZW9tX3RpbGUoKSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZT0iUmVkcyIsIGRpcmVjdGlvbiA9IDEpICsKICBnZW9tX3RleHQoZGF0YT0uICU+JSBkcGx5cjo6ZmlsdGVyKGlzX3NpZ25pZiksIGxhYmVsPSIqIiwgc2l6ZT01KQoKYGBgCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CmNvbm5lY3Rpdml0eV90ZXN0X2RmICU+JQogIGRwbHlyOjpncm91cF9ieShncm91cCkgJT4lCiAgZHBseXI6Om11dGF0ZShtZWFuX3ZhbD1tZWRpYW4oc2NvcmUpKSAlPiUKICBkcGx5cjo6dW5ncm91cCgpICU+JQogIGRwbHlyOjphcnJhbmdlKC1tZWFuX3ZhbCkgJT4lCiAgZHBseXI6Om11dGF0ZShncm91cD1mYWN0b3IoZ3JvdXAsIGxldmVscz11bmlxdWUoZ3JvdXApKSkgJT4lCiAgZ2dwbG90KGFlcyhvcmdhbiwgbG9nMXAoc2NvcmUpKSkgKwogIGdlb21fY29sKGZpbGw9ImdyZXkiKSArCiAgZ2VvbV9jb2woZGF0YT0uICU+JSBkcGx5cjo6ZmlsdGVyKGlzX3NpZ25pZiksIGFlcyhmaWxsPW9yZ2FuKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1vcmdfY29sb3JzKSAgKwogIGNvb3JkX2ZsaXAoKSArCiAgZmFjZXRfZ3JpZChncm91cH4uKSArCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTApKQpgYGAKCi0tLQo8IS0tICMjIyBGYWN0b3IgYW5ub3RhdGlvbiAgLS0+CjwhLS0gU28gZmFyIC0tPgoKPCEtLSAjIyMjIEZhY3RvciAxIC0tPgo8IS0tIENlbGwgY3ljbGUgLyBwcm9saWZlcmF0aW9uIHNpZ25hdHVyZSAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgMiAtLT4KPCEtLSBFeHBsYWlucyB2YXJpYXRpb24gaW4gbGF0ZSBCIGNlbGwgc3RhZ2VzLCBwb3NzaWJseSBkaWZmZXJlbmNlIGJldHdlZW4gQk0gYW5kIG90aGVyIG9yZ2Fucz8gLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDMgLS0+CjwhLS0gVGh5bXVzIHNwZWNpZmljIFQgY2VsbCBzaWduYXR1cmUsIGVzcGVjaWFsbHkgaW4gaW1tYXR1cmUgVCBjZWxscy4gSW50ZXJlc3RpbmdseSwgZGlmZmVyZW5jZSBhbHNvIGluIEIxIGNlbGxzLCBjb3VsZCBiZSBzaWduYWxsaW5nIGZyb20gdGh5bWljIG1pY3JvZW52aXJvbm1lbnQ/IC0tPgoKPCEtLSAjIyMjIEZhY3RvciA0IC0tPgo8IS0tIFZhcmlhdGlvbiB3aXRoaW4gcHJvZ2VuaXRvcnMsIGFuZCBsb3RzIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBpbiBCMSBjZWxscyB0b28hIFN0ZW1uZXNzIG1hcmtlcnMgc3VjaCBhcyBDRDM0LCBIT1BYLi4uIEV4cGxhaW5zIGxvdHMgb2YgdmFyaWFuY2UgaW4gVHJlZ3MgKDcuNjclKSAtLT4KCjwhLS0gIyMjIyBGYWN0b3IgNSAtLT4KPCEtLSBJTEMgc3BlY2lmaWMgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDcgLS0+CjwhLS0gQ291bGQgYmUgc2lnbmF0dXJlIG9mIHNwbGVlbiBzcGVjaWZpYyBwcm9nZW5pdG9ycywgb3Igc3BsZWVuIHNvdXAgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDggLS0+CjwhLS0gTW9yZSBjZWxsIGN5Y2xlL3Byb2xpZmVyYXRpb24sIGJ1dCBsb3dlciBpbiB0aHltdXMgc2FtcGxlcywgVEggc2FtcGxlcyBleHByZXNzIHByb3RlYXNvbWUgLS0+Cgo8IS0tICMjIyMgRmFjdG9yIDEwIC0tPgo8IS0tIG1hdHVyZSBWUyBwcm8gQiBjZWxscyAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDcsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKSAlPiUgLS0+CjwhLS0gICBsZWZ0X2pvaW4obW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXModmFsdWUsIGZpbGw9b3JnYW4pKSArIC0tPgo8IS0tICAgZ2VvbV9oaXN0b2dyYW0oKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkocFJPQykgLS0+Cgo8IS0tIGdldF9vcmdhbl9hdWMgPC0gZnVuY3Rpb24obW9mYV90cmFpbmVkLCBmLCBvLCBncm91cHMpeyAtLT4KPCEtLSAgICAgZGYgPC0gZ2V0X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gZiwgYXMuZGF0YS5mcmFtZSA9IFRSVUUsIGdyb3VwcyA9IGdyb3VwcykgJT4lIC0tPgo8IS0tICAgICBsZWZ0X2pvaW4obW9mYV90cmFpbmVkQHNhbXBsZXNfbWV0YWRhdGEpIC0tPgoKPCEtLSAgIGNhdCA8LSBhcy5udW1lcmljKGRmJG9yZ2FuPT1vKSAtLT4KPCEtLSAgIHByZWQgPC0gZGYkdmFsdWUgLS0+CjwhLS0gICBpZiAoc3VtKGNhdCkgPiAwKSB7IC0tPgo8IS0tICAgICByb2Nfb2JqIDwtIHJvYyhjYXQsIHByZWQpIC0tPgo8IS0tICAgICBhdWMgPC0gYXVjKHJvY19vYmopIC0tPgo8IS0tICAgICByZXR1cm4oYXMudmVjdG9yKGF1YykpIC0tPgo8IS0tICAgICB9IC0tPgo8IS0tIH0gLS0+Cgo8IS0tIHRvcF9ncl9kZiA8LSBsYXBwbHkoMToxOSwgZnVuY3Rpb24oZikgZGF0YS5mcmFtZSh0b3BfZ3JvdXA9Z2V0X3RvcF9jZWxsdHlwZV9wZXJfZmFjdG9yKG1vZmFfdHJhaW5lZCwgZiksIGZhY3Rvcj1mKSkgJT4lIC0tPgo8IS0tICAgcHVycnI6OnJlZHVjZShiaW5kX3Jvd3MpICAtLT4KCjwhLS0gb3JnID0gIkJNIiAtLT4KPCEtLSBBVUNfb3JnIDwtIHNhcHBseSgxOm5yb3codG9wX2dyX2RmKSwgZnVuY3Rpb24oaSl7IC0tPgo8IS0tICAgZ2V0X29yZ2FuX2F1Yyhtb2ZhX3RyYWluZWQsICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgbz1vcmcsIC0tPgo8IS0tICAgICAgICAgICAgICAgICBmPXRvcF9ncl9kZiRmYWN0b3JbaV0sICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgZ3JvdXBzID0gdG9wX2dyX2RmJHRvcF9ncm91cFtpXSl9IC0tPgo8IS0tICAgKSAtLT4KPCEtLSBBVUNfb3JnW3NhcHBseShBVUNfb3JnLCBpcy5udWxsKV0gPC0gTkEgLS0+CjwhLS0gdG9wX2dyX2RmW1siQVVDX29yZyJdXSA8LSB1bmxpc3QoQVVDX29yZykgLS0+Cgo8IS0tIGdncGxvdCh0b3BfZ3JfZGYsIGFlcyhmYWN0b3IsIGZpbGw9QVVDX29yZywgdG9wX2dyb3VwKSkgICsgLS0+CjwhLS0gICBnZW9tX3RpbGUoKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yb3VuZChBVUNfb3JnLCAyKSkpICsgLS0+CjwhLS0gICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpIC0tPgoKPCEtLSBgYGAgLS0+Cgo8IS0tIC0tLSAtLT4KCjwhLS0gYGBge3J9IC0tPgo8IS0tIGxpYnJhcnkoRW5zRGIuSHNhcGllbnMudjg2KSAtLT4KPCEtLSBoZy5wYWlycyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleGRhdGEiLCAiaHVtYW5fY3ljbGVfbWFya2Vycy5yZHMiLCBwYWNrYWdlPSJzY3JhbiIpKSAtLT4KPCEtLSBhbGxfZ2VuZXMgPC0gZW5zZW1ibGRiOjpnZW5lcyhFbnNEYi5Ic2FwaWVucy52ODYpIC0tPgoKPCEtLSBnZW5lX25hbWVfMl9pZCA8LSBmdW5jdGlvbihnZW5lKXsgLS0+CjwhLS0gICAgcmV0dXJuKGFsbF9nZW5lc1thbGxfZ2VuZXMkZ2VuZV9uYW1lPT1nZW5lLF0kZ2VuZV9pZFsxXSkgLS0+CjwhLS0gfSAtLT4KCjwhLS0gZ2VuZV9pZHMgPC0gc2FwcGx5KHJvd25hbWVzKHNjZSksIGdlbmVfbmFtZV8yX2lkKSAtLT4KPCEtLSByb3dEYXRhKHNjZSlbImdlbmVfaWQiXSA8LSBnZW5lX2lkcyAtLT4KPCEtLSByb3dEYXRhKHNjZSlbImdlbmVfbmFtZSJdIDwtIHJvd25hbWVzKHNjZSkgLS0+Cgo8IS0tIHJvd25hbWVzKHNjZSkgPC0gcm93RGF0YShzY2UpW1siZ2VuZV9pZCJdXSAtLT4KCjwhLS0gYXNzaWdubWVudHMgPC0gY3ljbG9uZShzY2UsIGhnLnBhaXJzLCBhc3NheS50eXBlPSJsb2djb3VudHMiKSAtLT4KCjwhLS0gIyMgQWRkICJwaGFzZSIgYXNzaWdubWVudHMgdG8gbW9mYSAtLT4KPCEtLSBzY2UkY2VsbGN5Y2xlX3BoYXNlIDwtIGFzc2lnbm1lbnRzJHBoYXNlcyAtLT4KPCEtLSBzYW1wbGVzX21ldGFkYXRhKG1vZmFfdHJhaW5lZCkgIDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSAlPiUgLS0+CjwhLS0gICBtdXRhdGUoY2VsbGN5Y2xlX3BoYXNlPXNjZVssbWF0Y2goc2FtcGxlc19tZXRhZGF0YShtb2ZhX3RyYWluZWQpJHNhbXBsZSwgY29sbmFtZXMoc2NlKSldJGNlbGxjeWNsZV9waGFzZSkgLS0+CjwhLS0gYGBgIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDEsIGNvbG9yX2J5ID0gImNlbGxjeWNsZV9waGFzZSIpIC0tPgo8IS0tIGBgYCAtLT4KCgo8IS0tIDwhLS0gYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD01fSAtLT4gLS0+CjwhLS0gPCEtLSBnZXRfZmFjdG9ycyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSAzLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgbXV0YXRlKG9yZ2FuID0gc2FwcGx5KHN0cl9zcGxpdChzYW1wbGUsICItIiksIGZ1bmN0aW9uKHgpIHhbbGVuZ3RoKHgpLTNdKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgZ3JvdXBfYnkoZ3JvdXApICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIG11dGF0ZShncl9tZWFuID0gbWVkaWFuKHZhbHVlKSkgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgdW5ncm91cCgpICU+JSAtLT4gLS0+CjwhLS0gPCEtLSAgIGFycmFuZ2UoZ3JfbWVhbikgJT4lIC0tPiAtLT4KPCEtLSA8IS0tICAgbXV0YXRlKGdyb3VwPWZhY3Rvcihncm91cCwgbGV2ZWxzPXVuaXF1ZShncm91cCkpKSAlPiUgLS0+IC0tPgo8IS0tIDwhLS0gICBnZ3Bsb3QoYWVzKG9yZ2FuLCB2YWx1ZSwgY29sb3I9b3JnYW4pKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgZ2VvbV9ib3hwbG90KCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGdlb21faml0dGVyKCkgKyAtLT4gLS0+CjwhLS0gPCEtLSAgICMgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGU9MikgKyAtLT4gLS0+CjwhLS0gPCEtLSAgIGNvb3JkX2ZsaXAoKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgZmFjZXRfd3JhcCgufmdyb3VwLCBzY2FsZXMgPSAiZnJlZV94IikgLS0+IC0tPgo8IS0tIDwhLS0gICAgICAgICAgICAgZ3JvdXBfYnkgPSAiZ3JvdXAiLCAgZG90X3NpemUgPSAwLjgsIGFkZF9ib3hwbG90ID0gVFJVRSwgZG9kZ2UgPSBUUlVFKSArIC0tPiAtLT4KPCEtLSA8IS0tICAgY29vcmRfZmxpcCgpIC0tPiAtLT4KPCEtLSA8IS0tIGBgYCAtLT4gLS0+CgoKPCEtLSAjIyBHbyBieSBjZWxsdHlwZSBpbnN0ZWFkIG9mIGZhY3RvciAtLT4KCjwhLS0gIyMjIERDMSAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gZ2V0X3ZhcmlhbmNlX2V4cGxhaW5lZChtb2ZhX3RyYWluZWQsIGFzLmRhdGEuZnJhbWUgPSBUUlVFKVtbMV1dICU+JSAtLT4KPCEtLSAgIGZpbHRlcihncm91cD09IkRDMSIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMoZmFjdG9yLCB2YWx1ZSkpICsgZ2VvbV9jb2woKSArIC0tPgo8IS0tICAgY29vcmRfZmxpcCgpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKGdyb3Vwfi4sIG5jb2wgPSA2LCBzY2FsZXMgPSAiZnJlZV94IikgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2ZhY3RvcnMobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gYygyLDQpLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGdyb3VwcyA9ICJEQzEiKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIGBgYHtyLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NH0gLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gYyg0KSwgY29sb3JfYnkgPSAib3JnYW4iLCBncm91cF9ieSA9ICJvcmdhbiIsIGdyb3VwcyA9ICJEQzEiKSAtLT4KPCEtLSBwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA0LCBncm91cF9ieSA9ICJncm91cCIsIGNvbG9yX2J5ID0gIm9yZ2FuIiwgZG90X3NpemUgPSAwLjgsIGFkZF9ib3hwbG90ID0gVFJVRSwgZG9kZ2UgPSBUUlVFKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvcnMgPSA0LCBuZmVhdHVyZXMgPSAzMCkgLS0+CjwhLS0gYGBgIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2RhdGFfc2NhdHRlcihtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDQsIGdyb3Vwcz0iREMxIiwgY29sb3I9Im9yZ2FuIiwgZmVhdHVyZXM9IkhMQS1EUkEiKSAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tICMjIEV4cGxvcmUgYnkgZmFjdG9yIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBwbG90X2ZhY3Rvcihtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDMpIC0tPgo8IS0tIHBsb3Rfd2VpZ2h0cyhtb2ZhX3RyYWluZWQsIGZhY3RvciA9IDMsIG5mZWF0dXJlcyA9IDIwKSAtLT4KPCEtLSBgYGAgLS0+CgoKPCEtLSAjIyBGaW5kIGZhY3RvcnMgdGhhdCBkaXNjcmltaW5hdGUgYmV0d2VlbiBvcmdhbnMgLS0+CgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gZ2V0X29yZ2FuX0FVQyA8LSBmdW5jdGlvbihtb2ZhX3RyYWluZWQsIGYsIGdyKXsgLS0+CjwhLS0gICBmX2RmIDwtIGdldF9mYWN0b3JzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IGYsIGdyb3VwcyA9IGdyLCBhcy5kYXRhLmZyYW1lID0gVFJVRSkgJT4lIC0tPgo8IS0tICAgICAjIGdyb3VwX2J5KGdyb3VwKSAlPiUgLS0+CjwhLS0gICAgICMgbXV0YXRlKHZhbHVlPXNjYWxlKHZhbHVlKSkgJT4lIC0tPgo8IS0tICAgICAjIHVuZ3JvdXAoKSAlPiUgLS0+CjwhLS0gICAgIG11dGF0ZShvcmdhbiA9IHNhcHBseShzdHJfc3BsaXQoc2FtcGxlLCAiLSIpLCBmdW5jdGlvbih4KSB4W2xlbmd0aCh4KS0zXSkpICAtLT4KPCEtLSAgIG9yZ2FucyA8LSB1bmlxdWUoZl9kZiRvcmdhbikgLS0+CjwhLS0gICBzdXBwcmVzc1dhcm5pbmdzKHN1cHByZXNzTWVzc2FnZXMoe29yZ19hdWMgPC0gc2FwcGx5KG9yZ2FucywgZnVuY3Rpb24ob3JnKSByb2MoYXMubnVtZXJpYyhmX2RmJG9yZ2FuPT1vcmcpLCBmX2RmJHZhbHVlKSRhdWMpfSkpIC0tPgo8IS0tICAgYWxsX29yZ2FucyA8LSBhcy5jaGFyYWN0ZXIodW5pcXVlKG1vZmFfdHJhaW5lZEBzYW1wbGVzX21ldGFkYXRhJG9yZ2FuKSkgLS0+CjwhLS0gICBvcmdfYXVjIDwtIHNldE5hbWVzKG9yZ19hdWNbYWxsX29yZ2Fuc10sIGFsbF9vcmdhbnMpIC0tPgo8IS0tICAgcmV0dXJuKG9yZ19hdWMpIC0tPgo8IS0tIH0gLS0+Cgo8IS0tIGFsbF9vcmdhbnMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRvcmdhbikpIC0tPgo8IS0tIGFsbF9ncm91cHMgPC0gYXMuY2hhcmFjdGVyKHVuaXF1ZShtb2ZhX3RyYWluZWRAc2FtcGxlc19tZXRhZGF0YSRncm91cCkpIC0tPgoKPCEtLSAjIyBNYXNrIGlmIHRvbyBsaXR0bGUgc2FtcGxlcyAtLT4KPCEtLSBuX3NhbXBsZXNfbWF0IDwtIHNhbXBsZXNfbWV0YWRhdGEobW9mYV90cmFpbmVkKSAlPiUgLS0+CjwhLS0gICBncm91cF9ieShvcmdhbiwgZ3JvdXApICU+JSAtLT4KPCEtLSAgIHN1bW1hcmlzZShuX3NhbXBsZXM9bigpKSAlPiUgLS0+CjwhLS0gICBwaXZvdF93aWRlcihpZF9jb2xzPWMoZ3JvdXApLCBuYW1lc19mcm9tPSJvcmdhbiIsIHZhbHVlc19mcm9tPSJuX3NhbXBsZXMiLCB2YWx1ZXNfZmlsbD0wKSAlPiUgLS0+CjwhLS0gICBjb2x1bW5fdG9fcm93bmFtZXMoImdyb3VwIikgJT4lIC0tPgo8IS0tICAgYXMubWF0cml4KCkgLS0+Cgo8IS0tIG1hc2tfcGFpcnMgPC0gdChuX3NhbXBsZXNfbWF0IDwgMykgLS0+Cgo8IS0tIEFVQ19tYXQgPC0gc2FwcGx5KGFsbF9ncm91cHMsIGZ1bmN0aW9uKGcpIGdldF9vcmdhbl9BVUMobW9mYV90cmFpbmVkLCBmPTEwLCBncj1nKSkgLS0+CjwhLS0gQVVDX21hdFttYXNrX3BhaXJzW3Jvd25hbWVzKEFVQ19tYXQpLCBjb2xuYW1lcyhBVUNfbWF0KV1dIDwtIE5BIC0tPgoKPCEtLSBBVUNfdGhyZXNoID0gMC44IC0tPgo8IS0tIHJlc2hhcGUyOjptZWx0KEFVQ19tYXQsIHZhcm5hbWVzPWMoIm9yZ2FuIiwgImdyb3VwIiksIHZhbHVlLm5hbWU9IkFVQyIpICU+JSAtLT4KPCEtLSAgIGdncGxvdChhZXMob3JnYW4sIGdyb3VwKSkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoYWVzKHNpemU9QVVDLCBjb2xvcj1BVUMpKSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChkYXRhPS4gJT4lIGZpbHRlcihBVUMgPiBBVUNfdGhyZXNoKSwgc2hhcGU9OCwgc2l6ZT0yLGNvbG9yPSJ3aGl0ZSIpICsgLS0+CjwhLS0gICBzY2FsZV9zaXplKGxpbWl0cyA9IGMoMC41LDEpKSArIC0tPgo8IS0tICAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNSwgIlJlZHMiKSkgLS0+CjwhLS0gYGBgIC0tPgoKCjwhLS0gYGBge3IsIGZpZy53aWR0aD0xNSwgZmlnLmhlaWdodD00fSAtLT4KPCEtLSBsaWJyYXJ5KHBhdGNod29yaykgLS0+CjwhLS0gcGxvdF9mYWN0b3IobW9mYV90cmFpbmVkLCBmYWN0b3JzID0gNSwgZ3JvdXBfYnkgPSAiZ3JvdXAiLCBjb2xvcl9ieSA9ICJvcmdhbiIsIGRvZGdlID0gVFJVRSwgYWRkX2JveHBsb3QgPSBUUlVFKSAgLS0+Cgo8IS0tICAgcGxvdF9sYXlvdXQoZ3VpZGVzPSJjb2xsZWN0IikgLS0+Cgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7cn0gLS0+CjwhLS0gcGxvdF93ZWlnaHRzKG1vZmFfdHJhaW5lZCwgZmFjdG9ycyA9IDUsIG5mZWF0dXJlcyA9IDMwKSAtLT4KPCEtLSBgYGAgLS0+CjwhLS0gYGBge3J9IC0tPgo8IS0tIHBsb3RfZGF0YV9oZWF0bWFwKG1vZmFfdHJhaW5lZCwgZmFjdG9yID0gNSwgc2hvd19jb2xuYW1lcz1GQUxTRSkgLS0+CjwhLS0gYGBgIC0tPgoKCgo8IS0tICMgTW9kZWwgMyAtICBNRUZJU1RPICAtLT4KCjwhLS0gQWRkIHRpbWUgYXMgY292YXJpYXRlIHRvIHJ1biBNRUZJU1RPIC0tPgoKPCEtLSBgYGB7cn0gLS0+CjwhLS0gIyMgVmVjdG9yIGZvciB0aW1lIGFzc2lnbm1lbnQgLS0+CjwhLS0gdGltZXMgPC0gZGlzdGluY3QoZGF0YS5mcmFtZShhZ2U9c2NlJGFnZSwgbmV3X3NhbXBsZSkpICU+JSAtLT4KPCEtLSAgIGNvbHVtbl90b19yb3duYW1lcygnbmV3X3NhbXBsZScpICU+JSAtLT4KPCEtLSAgIC5bc2FtcGxlX25hbWVzX3VuaXF1ZSxdIC0tPgoKPCEtLSBzYW1wbGVzX21ldGFkYXRhKG1vZmEpW1sidGltZSJdXSA8LSB0aW1lcyAtLT4KCjwhLS0gbW9mYSA8LSBzZXRfY292YXJpYXRlcyhtb2ZhLCBjb3ZhcmlhdGVzID0gInRpbWUiKSAtLT4KPCEtLSBtb2ZhIC0tPgo8IS0tIGBgYCAtLT4KPCEtLSBgYGB7ciwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTEwfSAtLT4KPCEtLSBnZ19pbnB1dCA8LSBwbG90X2RhdGFfb3ZlcnZpZXcobW9mYSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY292YXJpYXRlID0gVFJVRSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfZGltZW5zaW9ucyA9IFRSVUUpICAtLT4KPCEtLSBnZ19pbnB1dCAtLT4KPCEtLSBgYGAgLS0+Cgo8IS0tIDwhLS0gS2VlcCBncm91cHMgdGhhdCBzcGFuIG11bHRpcGxlIHZpZXdzIC0tPiAtLT4KPCEtLSA8IS0tIGBgYHtyfSAtLT4gLS0+CjwhLS0gPCEtLSBncl9zYW1wbGVzIDwtIHNwbGl0KHNhbXBsZXNfbWV0YWRhdGEobW9mYSkkc2FtcGxlLCBzYW1wbGVzX21ldGFkYXRhKG1vZmEpJGdyb3VwKSAtLT4gLS0+CjwhLS0gPCEtLSBhbGwoaXMubmEoZGF0YSRCTVssZ3Jfc2FtcGxlcyRCYXNvcGhpbF0pKSAtLT4gLS0+CjwhLS0gPCEtLSBsYXBwbHkodW5pcXVlKHNhbXBsZXNfbWV0YWRhdGEobW9mYSlbWyJncm91cCJdXSksIGZ1bmN0aW9uKHgpIGRhdGEkQk1bXSkgLS0+IC0tPgoKCjwhLS0gPCEtLSBtb2ZhQGRhdGEgLS0+IC0tPgo8IS0tIDwhLS0gc3Vic2UobW9mYSlbLHNhbXBsZXNfbWV0YWRhdGEobW9mYSlbWyJncm91cCJdXSA9PSAiQmFzb3BoaWwiXSAtLT4gLS0+CjwhLS0gPCEtLSBgYGAgLS0+IC0tPgoKPCEtLSBQcmVwYXJlIDQgdHJhaW5pbmcgLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBkYXRhX29wdHMgPC0gZ2V0X2RlZmF1bHRfZGF0YV9vcHRpb25zKG1vZmEpIC0tPgoKPCEtLSBtb2RlbF9vcHRzIDwtIGdldF9kZWZhdWx0X21vZGVsX29wdGlvbnMobW9mYSkgLS0+CjwhLS0gbW9kZWxfb3B0cyRudW1fZmFjdG9ycyA8LSAxMCAtLT4KCjwhLS0gdHJhaW5fb3B0cyA8LSBnZXRfZGVmYXVsdF90cmFpbmluZ19vcHRpb25zKG1vZmEpIC0tPgo8IS0tIHRyYWluX29wdHMkc2VlZCA8LSAyMDIwIC0tPgo8IS0tIHRyYWluX29wdHMkY29udmVyZ2VuY2VfbW9kZSA8LSAiZmFzdCIgIyB1c2UgImZhc3QiIGZvciBmYXN0ZXIgdHJhaW5pbmcgLS0+Cgo8IS0tIG1lZmlzdG9fb3B0cyA8LSBnZXRfZGVmYXVsdF9tZWZpc3RvX29wdGlvbnMobW9mYSkgLS0+CjwhLS0gbWVmaXN0b19vcHRzJHdhcnBpbmcgPC0gRkFMU0UgLS0+CjwhLS0gIyBtZWZpc3RvX29wdHMkc3BhcnNlR1AgPC0gVFJVRSAtLT4KCjwhLS0gbW9mYSA8LSBwcmVwYXJlX21vZmEoIC0tPgo8IS0tICAgb2JqZWN0ID0gbW9mYSwgLS0+CjwhLS0gICBkYXRhX29wdGlvbnMgPSBkYXRhX29wdHMsIC0tPgo8IS0tICAgbW9kZWxfb3B0aW9ucyA9IG1vZGVsX29wdHMsIC0tPgo8IS0tICAgdHJhaW5pbmdfb3B0aW9ucyA9IHRyYWluX29wdHMsIC0tPgo8IS0tICAgbWVmaXN0b19vcHRpb25zID0gbWVmaXN0b19vcHRzIC0tPgo8IS0tICkgIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gIyMgVHJhaW4gLS0+Cgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBvdXRmaWxlIDwtICIvbmZzL3RlYW0yMDUvZWQ2L2RhdGEvRmV0YWxfaW1tdW5lL215ZWxvaWRfbWVmaXN0b19tb2RlbC5oZGY1IiAtLT4KPCEtLSBtb2ZhX3RyYWluZWQgPC0gcnVuX21vZmEobW9mYSwgb3V0ZmlsZSA9IG91dGZpbGUpIC0tPgo8IS0tIGBgYCAtLT4KCjwhLS0gIyMgTG9hZCB0cmFpbmVkIG1vZGVsIC0tPgo8IS0tIGBgYHtyfSAtLT4KPCEtLSBtb2ZhX3RyYWluZWQgPC0gbG9hZF9tb2RlbChvdXRmaWxlLCBsb2FkX2ludGVycG9sX1ogPSBUUlVFKSAtLT4KPCEtLSBgYGAgLS0+Cgo=